缓存雪崩、穿透、热点key
缓存雪崩
- 概念
因为缓存失效,导致请求直接命中数据库。导致 DB 负荷大增,最终宕机。
- 如何解决
1)缓存高可用
通过搭建缓存的高可用,避免缓存挂掉导致无法提供服务的情况,从而降低出现缓存雪崩的情况。
假设我们使用 Redis 作为缓存,则可以使用 Redis Sentinel 或 Redis Cluster 实现高可用。
2)本地缓存
如果使用本地缓存(Ehcache、Guava Cache )时,即使分布式缓存挂了,也可以将 DB 查询到的结果缓存到本地,避免后续请求全部到达 DB 中。
3)请求 DB 限流(Guava RateLimiter、Sentinel)
通过限制 DB 的每秒请求数,避免 DB 宕机。这样至少能有两个好处:
可能有一部分用户,还可以使用,系统还没死透。
未来缓存服务恢复后,系统立即就已经恢复,无需在处理 DB 也挂掉的情况。
4)服务降级(Hystrix、Sentinel)
如果请求被限流,或者请求 DB 超时,我们可以服务降级,提供一些默认的值,或者友情提示。
缓存穿透
缓存穿透,是指查询一个不存在的数据,当缓存不命中时,会从 DB 查询到数据,再更新到缓存中,并且处于容错考虑,如果从 DB 查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询。
在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,这就是漏洞。比如使用程序瞬间产生大量的请求,攻击服务器。
- 如何解决
-
方案一,缓存空对象:当从 DB 查询数据为空,我们仍然将这个空结果进行缓存,具体的值需要使用特殊的标识,能和真正缓存的数据区分开。另外,需要设置较短的过期时间,一般建议不要超过 5 分钟。但是当客户端同一时刻发起大量请求时,会导致redis垃圾缓存过多,可能导致redis宕机。个人认为客户端同一时刻发起大量请求时,应该使用限流机制,避免大量垃圾请求进入后台。如果很多客户端发起DDOS攻击,就不太好防了。
-
方案二,使用互斥锁排队(或者分段锁),即根据key获取value值为空时,锁上,从数据库中load数据后再释放锁。若其它线程获取锁失败,则等待一段时间后重试。这里要注意,分布式环境中要使用分布式锁,单机的话用普通的锁(synchronized、Lock)就够了。
-
方案三,此方案摘自网络,BloomFilter 布隆过滤器:在缓存服务的基础上,构建 BloomFilter 数据结构,在 BloomFilter 中存储对应的 KEY 是否存在,如果存在,说明该 KEY 对应的值为空。那么整个逻辑的如下:
1、根据 KEY 查询缓存。如果存在对应的值,直接返回;如果不存在,继续向下执行。
2、根据 KEY 查询在缓存 BloomFilter 的值。如果存在值,说明该 KEY 不存在对应的值,直接返回空;如果不存在值,继续向下执行。
3、查询 DB 对应的值,如果存在,则更新到缓存,并返回该值。如果不存在值,更新到缓存 BloomFilter 中,并返回空。
热点key
在实际请求中,可能存在热点key的情况,导致缓存宕机。比如某个流量明星微博发了某条信息,几千万人都在看,这条微博就会成为热点key。
- 如何解决
识别热点key,如发现是热点key,缓存到本地。同时做好熔断、降级处理。盗用网络的一张图片(不记得出处了),如下: