41.如果避免缓存”击穿”的问题?

? 缓存击穿

缓存击穿,是指某个**极度“热点”**数据在某个时间点过期时,恰好在这个时间点对这个 KEY 有大量的并发请求过来,这些请求发现缓存过期一般都会从 DB 加载数据并回设到缓存,但是这个时候大并发的请求可能会瞬间 DB 压垮。

  • 对于一些设置了过期时间的 KEY ,如果这些 KEY 可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑这个问题。
  • 区别:
    • 和缓存“雪崩“”的区别在于,前者针对某一 KEY 缓存,后者则是很多 KEY 。
    • 和缓存“穿透“”的区别在于,这个 KEY 是真实存在对应的值的。

? 如何解决

有两种方案可以解决:

1)方案一,使用互斥锁。

请求发现缓存不存在后,去查询 DB 前,使用分布式锁,保证有且只有一个线程去查询 DB ,并更新到缓存。流程如下:

  • 1、获取分布式锁,直到成功或超时。如果超时,则抛出异常,返回。如果成功,继续向下执行。
  • 2、获取缓存。如果存在值,则直接返回;如果不存在,则继续往下执行。? 因为,获得到锁,可能已经被“那个”线程去查询过 DB ,并更新到缓存中了。
  • 3、查询 DB ,并更新到缓存中,返回值。

2)方案二,手动过期。

缓存上从不设置过期时间,功能上将过期时间存在 KEY 对应的 VALUE 里。流程如下:

  • 1、获取缓存。通过 VALUE 的过期时间,判断是否过期。如果未过期,则直接返回;如果已过期,继续往下执行。
  • 2、通过一个后台的异步线程进行缓存的构建,也就是“手动”过期。通过后台的异步线程,保证有且只有一个线程去查询 DB。
  • 3、同时,虽然 VALUE 已经过期,还是直接返回。通过这样的方式,保证服务的可用性,虽然损失了一定的时效性。

? 选择

这两个方案,各有其优缺点。

使用互斥锁 手动过期
优点 1、思路简单 2、保证一致性 1、性价最佳,用户无需等待
缺点 1、代码复杂度增大 2、存在死锁的风险 1、无法保证缓存一致性

具体使用哪一种方案,胖友可以根据自己的业务场景去做选择。

  • 有一点要注意,上述的两个方案,都是建立在**极度“热点”**数据存在的情况,所以实际场景下,需要结合 「如果避免缓存”穿透”的问题?」 的方案,一起使用。

另外,推荐看下 《Redis 架构之防雪崩设计:网站不宕机背后的兵法》 文章的 「三、缓存热点 key 重建优化」 ,大神解释的更好,且提供相应的图和伪代码。