redis击穿、雪崩问题
redis是开发中重度使用的中间件产品,在使用过程中踩过很多坑,遇到过击穿,雪崩,穿透等多种场景, 对上述场景总结出如下使用经验
1 、击穿
击穿指的是单个key在缓存中查不到,去数据库查询,这样如果数据量不大或者并发不大的话是没有什么问题的。
如果数据库数据量大并且是高并发的情况下那么就可能会造成数据库压力过大而崩溃.
方案:
1、设置永久value ,在业务顶峰期定时去更新数据,排队刷新缓存。
2、设置互斥锁(mutex key)
比如,redis的SETNX 去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
3、将击穿的key缓存起来,但是时间不能太长,下次进来是直接返回不存在,但是这种情况无法过滤掉动态的key,就是说每次请求进来都是不同额key,这样还是会造成这个问题
public function test($key){
$key_mutex = "key_mutex";
$redis = new \Redis;
$val = $redis->get($key);
if($val == null){
if($redis->setnx($key_mutex,"DB or function")){
$redis->set($key,"DB or function");
$redis->del($key_mutex);
return "DB or function";
}else{
return $redis->get($key);
}
} else {
return $val;
}
}
2、雪崩
雪崩指的是多个key查询并且出现高并发,缓存中失效或者查不到,然后都去db查询,从而导致db压力突然飙升,从而崩溃。
出现原因:
1、key同时失效
2、redis本身崩溃了
方案:
1、在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2、在应用层面控制减少查询的次数。
3、可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存。
4、不同的key,设置不同的过期时间,具体值可以根据业务决定,让缓存失效的时间点尽量均匀做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。(这种方式较复杂)
方案1和击穿的第1个方案类似,但是这样是避免不了其它key去查数据库,只能通过方案2 减少查询的次数。
public static function sget($key, $obtainCacheDataFunc = NULL, $expiry = 0)
{
$redis = new \Redis;
$cacheData = $redis->get($key);
if ($redis->exists($key) == 0) {
//从二级缓存更新数据
$cacheData = $redis->get("getSecondKey");
if (is_callable($obtainCacheDataFunc)) {
$lockGetCacheKey = "getTaskKey";
if ($redis->set($lockGetCacheKey, 'key is exist?', 'ex', 60, 'nx')) {
$cacheData = serialize($obtainCacheDataFunc($key));
if (!$redis->set($key, $cacheData, 'ex', $expiry)) Log::write('redis_set_error');
$redis->set("getSecondKey", $cacheData, 'ex', $expiry + 3600);
$redis->del([$lockGetCacheKey]);
}
}
}
return unserialize($cacheData);
}
3、穿透
一般是出现这种情况是,因为恶意频繁查询才会对系统造成很大的问题: key缓存并且数据库不存在,所以每次查询都会查询数据库从而导致数据库崩溃。
布隆过滤器是什么?
布隆过滤器可以理解为一个不怎么精确的 set 结构,当你使用它的 contains 方法判断某个对象是否存在时,它可能会误判。但是布隆过滤器也不是特别不精确,只要参数设置的合理,它的精确度可以控制的相对足够精确,只会有小小的误判概率。
当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。打个比方,当它说不认识你时,肯定就不认识;当它说见过你时,可能根本就没见过面,不过因为你的脸跟它认识的人中某脸比较相似 (某些熟脸的系数组合),所以误判以前见过你。
缺点:
1、会存在一定的误判率
2、对新增加的数据无法进行布隆过滤
3、数据的key不会频繁的更改
google的 gauva中有布隆过滤的实现
BloomFilter的关键在于hash算法的设定和bit数组的大小确定,通过权衡得到一个错误概率可以接受的结果。
我们设置的容错率越小那么过滤函数也就多,分配的空间也就越大(存放bits),那么误判率也就越小。
总结:其实关于redis击穿,穿透,雪崩基本都是大同小异,但是想实现高并发,高可用,高可靠的方案,还是要层层阻挡,从最前端的页面,到ip的限制,到网关,再到业务过滤和缓存,最好尽可能的减少落到DB的数据量。才能全面保证应用程序的高并发,高可用,高可靠。