Redis 缓存雪崩、击穿、穿透及解决方案
缓存雪崩:
某一时间点缓存大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机。
最简单的解决办法就是给每个Key的失效时间都加个随机值,这样可以保证数据不会在同一时间大面积失效。
缓存击穿:
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
最简单的解决办法就是可以将热点数据设置为永远不过期(后台异步去刷新);或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。
public function get($key, $expire_time='60') {
$value = $this->cache->get($key);
if ($value == null) {
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能从数据库查询获取数据
$key_mutex = 'KEY_NAME';
if ($this->cache->hsetnx($key_mutex, 1, 3*60) == 1) {
$value = '从数据库中获取数据';
$this->cache->save($key, $value, $expire_time);
$this->cache->hdel($key_mutex);
} else {//其他线程已经查询到数据并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
$this->get($key);//重试
}
} else {
return $value;
}
}
缓存穿透:
查询一个不存在的数据,就是缓存和数据库都查不到这个数据,每次都会去数据库查询,从而可能压垮数据库。
最简单的解决办法就是当查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟(时间长短可以自己评估)。当然,还有一个办法就是采用Redis的布隆过滤器(Bloom Filter),将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。