引入缓存竟然会带来这么多问题?( 二 )


缓存穿透缓存穿透: 当请求过来,访问不存在的数据时(即既不在缓存中,也不在数据库中),这会导致访问缓存,未命中,继续访问数据库db,然后发现在数据库中还是未查询到数据,这个时候也就不能回写缓存,来为后续的请求服务;也就是说 , 当这种请求过来,每次都会去查数据库 , 缓存形同虚设 , 一旦流量暴增 , 容易直接带崩数据库

引入缓存竟然会带来这么多问题?

文章插图
图片
这种不存在的数据可能被管理员误删,也有可能被黑客恶意利用(恶意请求) , 不断地去试 , 一旦发现一个不存在的数据 , 就拼命发请求访问这个数据 , 直到数据库锁住
那解决办法也很简单,常见的有:
  1. 比如每次访问数据如果既不在缓存中,也不在数据库中,那就缓存一个占位符或者空值,过期时间也不要设置过长,比如1分钟就行,这样的话 , 在1分钟内 , 这么多请求只有一次能直接访问数据库 , 这样就能显著降低数据库的压力;如果缓存过期时间过长,会出现大量的空缓存,进而导致缓存资源的浪费
  2. 还可以针对请求携带的参数,比如是那种特殊字符、非法字符等,我们数据库肯定不会存这些东西,直接在应用服务层进行限制,不允许访问
  3. 还可以通过第三方组件来实现,比如布隆过滤器,其主要是其特性:布隆过滤器判断一个元素不在集合中,那肯定就不在 。如果判断存在,那有一定可能性它在说谎,具体原理可以参考笔者以前的一篇文章海量数据处理的利器-布隆过滤器 。在缓存和数据库之间再加上布隆过滤器 , 通过布隆过滤器快速判断数据是否存在 , 从而避免多次之间请求数据库
缓存击穿在我们正常的业务之中 , 总有一些数据会被频繁访问,这就是热点数据
所谓的缓存击穿指的是,缓存中热点数据的key过期失效,由于是热点数据,在过期的一瞬间会有大量的请求过来(高并发),这些请求,最终都会直接访问数据库,这样数据库很容易被打垮 , 缓存仿佛被"击穿"了
常见的解决方案:
  1. 加锁 , 进程锁/分布式锁,当请求过来时,缓存未命中时,会通过锁将这个缓存key锁上,等当这个请求从数据库获取数据后再回写到缓存中后 , 再释放锁;期间其他请求过来 , 会获取锁失败 , 等待一段时间重试,就可以直接读取缓存了 。需要注意的是,如果业务量不大,进程锁就够了的话 , 也就没必要上分布式锁 , 多引入额外组件,就会增加系统的不稳定性

引入缓存竟然会带来这么多问题?

文章插图
图片
还可以继续改进,将请求2未获得锁,直接返回,升级成自旋锁,它不直接返回 , 而是等待一会重新尝试获取锁,这种高并发情况下,只有唯一请求是db请求,所有请求共享结果
  1. 给缓存的Key设置合理的过期时间并加上随机值,尽量减少缓存短期大量失效,出现大量访问数据库的情况 , 实现"削峰填谷"
  2. 网上有文章提出 , 可以让热点数据的缓存不设置过期时间,这样不就可以永不过期嘛,但这其实是个很危险的操作
使用缓存的前提是一定要设置过期时间,因为由于项目会不断迭代更新,业务不断复杂,开发人员更替 , 缓存会变得越来越难以维护,另外缓存和数据库无法避免的数据不一致的情况,缓存的过期时间其实就是兜底,防止缓存和数据库数据长时间不一致
我们还可以通过消息队列来间接地让热点数据的缓存延期,当热点缓存过期时 , 后台服务再检测更新缓存,防止缓存击穿;至于是否延期,得做访问量分析与统计,当然引入新的组件也会带来额外的稳定性问题,还是得根据业务情况 , 实事求是
缓存雪崩缓存雪崩,指定是大量请求未命中缓存,直接访问数据库,导致数据库压力过大 , 倘若请求足够的多,会直接将数据库压垮,继而影响整个系统,如同"雪崩"
个人感觉缓存击穿是缓存雪崩的一个子集,缓存雪崩一般有2种诱因:缓存服务异常 , 比如redis故障宕机或者缓存服务是正常的 , 但大量缓存数据在同一时间过期


推荐阅读