Redis读书笔记(三)应用与设计:缓存击穿、缓存穿透和缓存雪崩

缓存穿透

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存层.
缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。 缓存穿透问题可能会使后端存储负载加大,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕掉。

如何发现?

通常可以在程序中分别统计总调用数、缓存层命中数、存储层命中数,如果发现大量存储层空命中,可能就是出现了缓存穿透问题。

造成缓存穿透的基本原因有两个:

  • 自身业务代码或者数据出现问题
  • 一些恶意攻击、爬虫等造成大量空命中。

解决方案

校验

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;

缓存空对象

当存储层不命中后,仍然将空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源。
缓存空对象会有两个问题:

  • 空值做了缓存,意味着缓存层中存了 更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
  • 缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方 式清除掉缓存层中的空对象
布隆过滤器拦截

在访问缓存层和存储层之前,将存在的key用布隆过滤 器提前保存起来,做第一层拦截。

有关布隆过滤器的相关知识,可以参考:https://en.wikipedia.org/wiki/Bloom_filter可以利用Redis的Bitmaps实现布隆过滤器,GitHub上已经开源了类似的方案,读者可以进行参
考:https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter。

这种方法适用于数据命中不高、数据相对固定、实时性低(通常是数据集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少。

方案对比

在这里插入图片描述

缓存雪崩

由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。

解决方案

  • 保证缓存层服务高可用性。例如使用Redis Sentinel和Redis Cluster方案。
  • 依赖隔离组件为后端限流并降级
  • 提前演练。在项目上线前,演练缓存层宕掉后,应用以及后端的负 载情况以及可能出现的问题,在此基础上做一些预案设定。
  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  • 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
  • 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

在实际项目中,我们需要对重要的资源(例如Redis、MySQL、 HBase、外部接口)都进行隔离,让每种资源都单独运行在自己的线程池 中,即使个别资源出现了问题,对其他服务没有影响。但是线程池如何管 理,比如如何关闭资源池、开启资源池、资源池阀值管理,这些做起来还是 相当复杂的。这里推荐一个Java依赖隔离工具 Hystrix(https://github.com/netflix/hystrix),如图11-15所示。Hystrix是解决依 赖隔离的利器,但是该内容已经超出文章的范围,同时只适用于Java应用, 所以这里不会详细介绍。

缓存击穿

开发人员使用“缓存+过期时间”的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。
但是有两个问题如果同时出现,可能就会对应用造成致命的危害:

  • 当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。
  • 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次IO、多个依赖等。 在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。

解决方案

  • 互斥锁(mutex key) 此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行 完,重新从缓存获取数据即可.
  • 永远不过期 :“永远不过期”包含两层意思:从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。 从实战看,此方法有效杜绝了热点key产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不 一致。

作为一个并发量较大的应用,在使用缓存时有三个目标:

  • 加快用 户访问速度,提高用户体验。
  • 降低后端负载,减少潜在的风险,保证系统平稳。
  • 保证数据“尽可能”及时更新。

下面将按照这三个维度对上 述两种解决方案进行分析。 ·
互斥锁(mutex key):这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好
使用互斥锁重建缓存

“永远不过期”:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大
“永远不过期”策略

方案对比

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值