缓存雪崩、缓存穿透、缓存击穿等问题的应对
缓存雪崩
情况:大量缓存设置了相同的过期时间,导致某一刻同时失效,全部请求都到达db,造成db压力过大崩溃。或者缓存服务器出了故障,许多数据查不到缓存,直接请求到了db。
应对:
- 可以在key的失效时间上加点随机值,避免集体失效。
- 也可以设置缓存永久有效。然后通过后台线程来更新,而不是业务线程更新。
- 缓存失效后,用加锁或队列的方式保证缓存的单线程写,从而只有一个线程落入db,后面的都能继续从缓存中获取。
- 对于缓存服务器故障进行预防–高可用架构(用Redis Sentinel 和 Redis Cluster 实现高可用)
- 如果缓存出了故障,要设计限流、熔断、降级方案,保证核心服务还能用。(可以配置开关人工降级,或者根据一些关键数据设计自动降级。 降级策略、规则(哪些边缘服务不提供,那些必须提供等)、发送报警这些需要实现考虑配套)
- 准备好redis数据备份和恢复(做好持久化)、缓存预热这些方案,事发后快速恢复缓存数据。
- 可以提前演练,尝试缓存出问题系统如何应对,如何快速解决。
缓存穿透
情况:查询一个不存在的数据,缓存中查不到,就会去存储层查询。在高并发情况下,如果有人故意查询不存在的key,则大量请求穿透了缓存层,形成一种对db直接的攻击。
应对:
- 对key设置规则,过滤掉不符合规则的key
- 把空结果作为一个值存入缓存,保持一个较短的有效时间。
- 或者使用布隆过滤器(BloomFilter),将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
缓存击穿
情况:不同于缓存穿透(没命中),这里指缓存成为“热点”,有大量高并发请求击中它,而如果这个缓存失效瞬间仍很多请求,会造成大量请求压到db,可能压垮db。
应对:
- 使用redis互斥锁,保障只有一个请求去读db,然后存入缓存,其他的请求不会到db。(使用set lock-xxx true ex 5 nx,一条原子指令,相当于setnx lock-xxx true 与 expire lock-xxx 5合并)
请求代码实例大致如下:(代码并不完美,比如$redis和$db直接这样出现会报错,只是写出来表示一下思路)
public get($key) { $value = $redis->get($key); //先从redis查询 if ($value == null) { if ($redis->setnx($key_mutex, 1, 60) == 1) { $value = $db->get($key);//从db中查询数据 $redis->set($key, $value, $expire_secs=300); $redis->del($key_mutex); } else { sleep(1); get($key); } } else { return $value; } }
热点数据问题
情况:遇到到哪些数据可能是热点,考虑应对热点数据查询。
应对:提前预热,把数据存入缓存中,避免请求时查询db。对于特别热门的,某一条请求量极端大,可以复制多份,把请求分散到多台缓存服务器上。