Redis的缓存雪崩、穿透、击穿
缓存雪崩
高并发条件下,大面积的缓存key在同一时间失效,导致sql负载过高而宕机。
解决方案:
- 不同的key,设置不同的失效时间,让缓存失效的时间点尽量均匀(可采用随机)
- 在缓存失效后,通过加锁或者队列来控制读取数据库写缓存的线程数量
- 通过缓存reload机制,预先去更新缓存,在即将发生大并发访问前手动触发加载缓存(定时任务)
- 使用二级缓存或者双缓存策略,使用redis集群部署,将热点数据均匀分布在不同节点,单个节点宕机可从其他节点上获取相关数据
缓存穿透
对于redis缓存和数据库中不存在的数据,因为redis中没有对应数据,无法进行拦截,请求会直接被穿透到数据库。若有人恶意进行破坏,进行高并发请求不能存在的KEY,会导致数据库压力过大而宕机。
解决方案:
- 使用布隆过滤器(将所有可能存在的数据hash到一个足够大的bitmap,一个一定不存在的数据会被这个bitmap拦截),不存在的key会被布隆过滤器过滤,从而减轻数据库的压力
- 对不存在的数据也缓存到redis里,设置value为null
- 缓存太多空值,占用更多时间(优化:设置为短期失效) - 存储层更新代码了,缓存层还是空值(优化:后台设置时主动删除空值,并将值进行缓存)
- 其他:拉黑恶意IP、请求参数校验
缓存击穿
对于设置了过期时间的KEY,如果这些KEY可能在某些时间被高并发访问,成为热点KEY,失效的一瞬间,持续的高并发访问会击穿缓存直接访问数据库, 导致数据库压力骤升而宕机。
另外地,缓存的构建是需要一定时间的。(可能是一个复杂计算,例如复杂的sql、多次IO、多个依赖等等),于是就会出现一个致命问题:在缓存失效的瞬间,有大量线程来构建缓存,造成后端负载加大,甚至可能会让系统崩溃 。
解决方案:
- 使用互斥锁(mutex key):只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据
- “提前”使用互斥锁:在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。
- “永远不过期”:
这里的“永远不过期”包含两层意思: (1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。 (2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期
- 资源保护:可以做资源的隔离保护主线程池,如果把这个应用到缓存的构建也未尝不可。
方案对比:
作为一个并发量较大的互联网应用,我们的目标有3个:
- 加快用户访问速度,提高用户体验。
- 降低后端负载,保证系统平稳。
- 保证数据“尽可能”及时更新(要不要完全一致,取决于业务,而不是技术。)
四种方案,可以做如下比较,还是那就话:没有最好,只有最合适。
解决方案 | 优点 | 缺点 |
---|---|---|
简单分布式锁 | 1. 思路简单 2. 保证一致性 | 1. 代码复杂度增大 2. 存在死锁的风险 3. 存在线程池阻塞的风险 |
加另外一个过期时间 | 1. 保证一致性 | 同上 |
不过期 | 1. 异步构建缓存,不会阻塞线程池 | 1. 不保证一致性。 2. 代码复杂度增大(每个value都要维护一个timekey)。 3. 占用一定的内存空间(每个value都要维护一个timekey)。 |
资源隔离组件hystrix | 1. hystrix技术成熟,有效保证后端。 2. hystrix监控强大。 | 1. 部分访问存在降级策略。 |