Redis 缓存击穿、穿透、雪崩的原因以及解决方案
更多文章请查看我的博客
前因
最近搞了个redis作为记录一些频发请求以及一些经常访问,但是访问页面的数据量较大的页面。刚开始的时候没有什么问题,但是当运营到一段时间后,发现了些问题,经过查阅资料,解决了问题。所以在这里记录下。
缓存穿透
原因描述–缓存穿透
指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。
但是这种方法存在一个问题,比如我传一个用户id为-1,这个用户id在缓存里面是肯定不存在的,所以会去数据库里面查询,如果有搞事情的人,大批量请求并传用户id为-1,那就和没用redis一样,导致数据库压力过大而崩溃。
解决方法–缓存穿透
方法一:在接口层增加校验,不合法的参数直接返回。不相信任务调用方,根据自己提供的API接口规范来,作为被调用方,要考虑可能任何的参数传值。
方法二:在缓存查不到,DB
中也没有的情况,可以将对应的key的value写为null
,或者其他特殊值写入缓存,同时将过期失效时间设置短一点,以免影响正常情况。这样是可以防止反复用同一个ID来暴力攻击。
方法三:正常用户是不会这样暴力功击,只有是恶意者才会这样做,可以在网关NG作一个配置项,为每一个IP设置访问阈值。
方法四:高级用户布隆过滤器(Bloom Filter),这个也能很好地防止缓存穿透。原理就是利用高效的数据结构和算法快速判断出你这个Key是否在DB
中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。
缓存雪崩
原因描述–缓存雪崩
在同一个时间,缓存大批量的失效,然后所有请求都打到DB数据库,导致DB数据库直接扛不住崩了。
比如,电商首页缓存,如果首页的key全部都在某一时刻失效,刚好在那一时刻有秒杀活动,那这样的话就所有的请求都被打到了DB。并发大的情况下DB必然扛不住,没有其他降级之类的方案的话,DBA也只能重启DB,但是这样又会被新的流量搞挂。
解决方法–缓存雪崩
批量往redis存数据的时候,把每个key的失效时间加上个随机数,比如1-5分钟随机,这样的话就能保证数据不会在同一个时间大面积失效。
缓存击穿
原因描述–缓存击穿
缓存击穿跟缓存雪崩有些类似,雪崩是大面积缓存失效,导致数据库崩溃,而缓存击穿是一个key是热点,不停地扛住大并发请求,全都集中访问此key,而当此key过期瞬间,持续的大并发就击穿缓存,全都打在DB上。就又引发雪崩的问题。
解决方法–缓存击穿
方法一:把这个热点key设置为永久有效。
方法二:使用互斥锁,这是比较常用的方法,简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去查询数据库,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行查询数据库的操作并回设缓存;否则,就重试整个get缓存的方法。
互斥代码:
function get($key){
$value = $redis->get($key);
if($value == null){
//不存在,设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能查询数据库
if ($redis->setnx("key_mutex", 1, 3 * 60) == 1){
$value = "";//这是查询数据库文件
$redis->set(key, value, expire_secs);
$redis->del(key_mutex);
}else{
//这个时候代表同时候的其他线程已经查询数据库并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key); //重试
}
}else{
//存在则直接返回
return $value;
}
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
Mark
感谢分享
有点小瑕疵 阀值 -> 阈值
@wtySk 感谢指出问题
很有收获,谢谢分享。清晰明了。
get(key)
递归,缺少return
:grinning: