Redis-缓存一致性,缓存穿透,缓存击穿,缓存雪崩

缓存问题
一致性
在使用缓存的过程中往往会由于不正确的使用方式,导致缓存和后端数据库数据不一致。但是并不是一定需要强一致性,需要根据业务场景来判断,缓存共有多种使用模式,常用三种 Cache Aside Pattern, Cache Through Pattern 和 Cache Back Pattern(具体介绍可以参考这篇文章Things You Should Know About Database Caching)。

在缓存+数据库的架构中,缓存和数据库的操作总共有以下几种顺序:

  1. 先更新缓存,再更新数据库
    业务方无法保证两个写操作都成功。当更新缓存成功,更新数据库失败时,数据库中就是旧数据。
  2. 先更新数据库,再更新缓存
    在多线程并发时,会出现不一致行为。假设请求 A 先操作数据库,请求 B 后操作数据库,但是可能存在请求 B 先写缓存,请求 A 后写缓存的情况,从而导致数据库与缓存之间的数据不一致。
  3. 先写数据库,再删除缓存
    业务要保证写数据库和删除缓存是一个原子操作,否则写数据成功,删除缓存失败就会出现数据不一致的问题。
  4. 先删缓存, 再写数据库
    先删除缓存,再写数据库前,可能另外一个读请求, 在缓存未命中时, 从数据库获取到旧值,将其放到缓存。 这时缓存中是旧值,数据库是新值。
    通过上面分析可以得出,业务方无论是先写缓存还是先写数据库,最终都有可能出现缓存和数据库的不一致。那到底如何才能使缓存和数据库达到一致的状态。针对这个问题, 业界通常使用下面两种解决方案: 1)采用延时双删策略 2)异步更新缓存策略 。

延时双删策略
写流程如下

先删除缓存,再写数据库
休眠一段时间(比如500毫秒)
再次删除缓存(如果删除失败了,还需要业务方实现重删机制,因此具有一定侵入性)
读流程:先读缓存,当缓存未命中,再从数据库中读取,然后再写入缓存。

需要休眠一段时间再删除是因为,如果A写数据,先删缓存再写数据库,此时B来读了,则会读到旧值并更新到缓存中,如果A在写数据库后马上删缓存,可能并不能删除旧值。因此需要等待一段时间,等待B在redis写缓存成功后,再将缓存删除。

但是有一个问题是,在B写成功-A删缓存前的休眠期间,如果有其他的线程来读,则读取的还是旧值,因此还是不具有强一致性的。

另外,休眠也降低了整个系统的性能,重删机制也对代码的侵入性比较大。因此这种方式虽然简单,但性能和侵入性都不好。

异步更新策略

写流程:

  1. 先删除缓存,再写数据库

  2. 额外组件通过解析从库 bin log, 将写操作发送到消息队列

  3. 缓存从消息队列中消费,更新缓存
    读流程:

  4. 先从缓存读取

  5. 如果缓存未命中, 从数据库读取, 将数据发送到消息队列。

  6. 缓存从消息队列消费,更新缓存。
    异步更新缓存策略通过消息队列的方式将并行化的操作串行化,从而解决了并发问题。但是也引入一些其他问题,比如缓存相对主库落后延迟较大。

优点:1.串行化操作,无并发也就无一致性问题。2.对业务代码无侵入性

缺点:1.比较复杂 2.缓存相对主库落后延迟较大。

利用版本号原子更新
*先更新数据库,再更新缓存,并引入版本号

再没有引入版本号之前:

1.A 先操作数据库。

2.请求 B 后操作数据库,并写缓存。

3.A在B后写缓存,此时旧值覆盖了新值,业务读取到的就都是旧值了。

引入版本号之后:

1.A查询数据库,并记录当前版本号Version。

2.B更新数据库,将version+1,并更新缓存。

3.A后写缓存,但此时发现版本号不一致,则不会执行更新,即不会将旧值覆盖新值,而是直接读取新值。

优点:简单,且解决了一致性的问题

缺点:业务中多了version字段,对代码入侵大

问题
缓存穿透
缓存穿透是指查询数据库不存在的数据。则每次都会直接从数据库中读取,造成数据库压力。

解决方案:

对于不存在的key,也进行缓存,value为null。
维护一个bitMap,将可能的key都存放在BitMap中。查询前先从bitMap中查询,看是否存在,存在再查数据库。
缓存击穿
如果某些 Key 可能会在某些时间点被超高并发地访问,则它是一种非常“热点”的数据。缓存击穿是指大量的请求同时查询一个失效的热点数据,导致请求全部转发到数据库。

解决方案:

key物理上永不过期,但是逻辑上过期。
如果key不存在,阻塞当前Key的请求,如果有其他的当前key请求,也进行阻塞。然后去数据库中加载数据并缓存。缓存成功后再重试get缓存的方法。
缓存雪崩
设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多key,击穿是某一个key缓存。

将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如1-5秒随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件;
使用互斥锁,当缓存数据失效时,保证只有一个请求能够访问到数据库,并更新缓存,其他线程等待并重试;
使用redis集群,及时缓存失效或故障,也能通过备份的redis进行服务;
持久化

RDB:在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。缺点:可能产生数据丢失

AOF:记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。缺点:AOF 文件的体积通常要大于 RDB 文件的体积,速度要慢点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值