Redis作为缓存可能会出现的问题及解决方案
所以我觉得很有必要总结一下Redis作为缓存使用,可能会引发的问题。以达到温故而知新的效果
ps:在本文章中,就不讨论Redis能用来干啥?这种基础问题了
在讨论Redis作为缓存使用,可能会引发的问题之前,我们得了解官方是怎么定义Redis
Redis是一个开源的内存中的数据结构存储系统,它可以用作:数据库、缓存和消息中间件。
它支持多种类型的数据结构,如字符串(Strings),散列(Hash),列表(List),集合(Set),有序集合(Sorted Set或者是ZSet)。
Redis 内置了复制(Replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(Transactions) 和不同级别的磁盘持久化(Persistence),并通过 Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(High Availability)。
Redis也提供了两种持久化策略,这些策略可以让用户将自己的数据保存到磁盘上面进行存储。根据实际情况,可以每隔一定时间将数据集以快照的形式保存在磁盘(RDB策略),或者将所有操作成功的命令追加到命令日志中(AOF策略),它会在执行命令时,将被执行的命令复制到硬盘里面,实现实时持久化数据的效果。当然,根据实际开发的需求,你也可以关闭持久化功能,单纯的将Redis作为一个高效的网络的缓存数据功能使用。
二、Redis作为缓存使用,可能会引发的问题(重点)
Redis由C语言开发,并且将数据存储在内存中,可以说Redis完全是基于内存进行操作,对数据读写的速度极快、性能极好。官方提供的数据是可以达到每秒100000+的吞吐量(每秒内查询次数),如此优秀的机制使Redis极其适合作为缓存使用。
1.缓存穿透
程序在处理缓存时,一般是先从缓存查询,如果缓存没有这个key(理解为数据),则会从数据库中查询,并将查询到的数据保存到缓存中去。
好,问题来了,如果有个坏心眼的人向服务器发起请求,去查询一个一定不存在的数据,由于缓存中没有查到对应的数据时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,缓存形同虚设,这就是缓存穿透。
解决方案:
1)最粗暴也是最常用的方法就是,如果一个查询的数据为空(不管是数据不存在还是系统故障),我们就把这个空结果进行缓存,但要把它的过期时间设置得很短,最长不超过5分钟,这样能有效的解决缓存穿透问题。
2)其次,可以采用布隆过滤器,也能解决缓存穿透问题
2.缓存击穿
缓存击穿和缓存穿透在本质上很相似,都是查询数据时缓存失去了作用,导致请求直接去数据库查询数据,但是造成缓存失效的原因却是天差地别。
大量用户在同一时间内访问某热点数据时,存储在缓存中的热点数据却突然失效(过期时间),结果就是大量的请求直接访问数据库,使数据库的压力变大,甚至导致数据库宕机,这就是缓存击穿。
解决方案:比较常用的方法是加互斥锁(mutex)保证数据的一致性。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去访问数据库,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set另一个请求所需要的数据,当操作返回成功时,再进行访问数据库的操作并回设缓存;否则,就重试整个get缓存的方法。
3.缓存雪崩
如果缓存的数据集中在一段时间内大批失效,而不巧的是在这段时间内又有大量用户发起请求访问数据,这样就会造成大量的缓存击穿,所有的请求都会直接去访问数据库,导致数据库在短时间内宕机,这就是缓存雪崩。
ps:这里我使用了夸张的修辞手法。缓存雪崩不一定会造成数据库宕机,但缓存如果发生雪崩现象,那肯定是很严重的
解决方案:
1)加锁排队。加互斥锁,添加信息队列
2)数据预热。可以通过缓存Reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
3)设置热点缓存永不过时
笔者: 以上问题及解决方案纯粹个人见解,如果有错误的地方,还请指正
Redis是个大话题,只要是去面试Java开发,几乎必问。基础一点的问Redis是什么东西?用来做什么?Redis支持哪些数据类型?Redis的性能为什么那么好?复杂一点的就会问到缓存穿透、缓存击穿、缓存雪崩等问题。而我在面试的时候也被问到了Redis为什么用来做缓存的问题。
所以我觉得很有必要总结一下Redis作为缓存使用,可能会引发的问题。以达到温故而知新的效果
ps:在本文章中,就不讨论Redis能用来干啥?这种基础问题了
在讨论Redis作为缓存使用,可能会引发的问题之前,我们得了解官方是怎么定义Redis
Redis是一个开源的内存中的数据结构存储系统,它可以用作:数据库、缓存和消息中间件。
它支持多种类型的数据结构,如字符串(Strings),散列(Hash),列表(List),集合(Set),有序集合(Sorted Set或者是ZSet)。
Redis 内置了复制(Replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(Transactions) 和不同级别的磁盘持久化(Persistence),并通过 Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(High Availability)。
Redis也提供了两种持久化策略,这些策略可以让用户将自己的数据保存到磁盘上面进行存储。根据实际情况,可以每隔一定时间将数据集以快照的形式保存在磁盘(RDB策略),或者将所有操作成功的命令追加到命令日志中(AOF策略),它会在执行命令时,将被执行的命令复制到硬盘里面,实现实时持久化数据的效果。当然,根据实际开发的需求,你也可以关闭持久化功能,单纯的将Redis作为一个高效的网络的缓存数据功能使用。
二、Redis作为缓存使用,可能会引发的问题(重点)
Redis由C语言开发,并且将数据存储在内存中,可以说Redis完全是基于内存进行操作,对数据读写的速度极快、性能极好。官方提供的数据是可以达到每秒100000+的吞吐量(每秒内查询次数),如此优秀的机制使Redis极其适合作为缓存使用。
1.缓存穿透
程序在处理缓存时,一般是先从缓存查询,如果缓存没有这个key(理解为数据),则会从数据库中查询,并将查询到的数据保存到缓存中去。
好,问题来了,如果有个坏心眼的人向服务器发起请求,去查询一个一定不存在的数据,由于缓存中没有查到对应的数据时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,缓存形同虚设,这就是缓存穿透。
解决方案:
1)最粗暴也是最常用的方法就是,如果一个查询的数据为空(不管是数据不存在还是系统故障),我们就把这个空结果进行缓存,但要把它的过期时间设置得很短,最长不超过5分钟,这样能有效的解决缓存穿透问题。
2)其次,可以采用布隆过滤器,也能解决缓存穿透问题
2.缓存击穿
缓存击穿和缓存穿透在本质上很相似,都是查询数据时缓存失去了作用,导致请求直接去数据库查询数据,但是造成缓存失效的原因却是天差地别。
大量用户在同一时间内访问某热点数据时,存储在缓存中的热点数据却突然失效(过期时间),结果就是大量的请求直接访问数据库,使数据库的压力变大,甚至导致数据库宕机,这就是缓存击穿。
解决方案:比较常用的方法是加互斥锁(mutex)保证数据的一致性。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去访问数据库,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set另一个请求所需要的数据,当操作返回成功时,再进行访问数据库的操作并回设缓存;否则,就重试整个get缓存的方法。
3.缓存雪崩
如果缓存的数据集中在一段时间内大批失效,而不巧的是在这段时间内又有大量用户发起请求访问数据,这样就会造成大量的缓存击穿,所有的请求都会直接去访问数据库,导致数据库在短时间内宕机,这就是缓存雪崩。
ps:这里我使用了夸张的修辞手法。缓存雪崩不一定会造成数据库宕机,但缓存如果发生雪崩现象,那肯定是很严重的
解决方案:
1)加锁排队。加互斥锁,添加信息队列
2)数据预热。可以通过缓存Reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
3)设置热点缓存永不过时
笔者: 以上问题及解决方案纯粹个人见解,如果有错误的地方,还请指正