缓存一致性策略

概念

业务中通常使用MySQL作为持久化数据库,并使用缓存来提升系统性能,同时使用缓存和数据库会存在数据一致性问题,而缓存一致性策略,或者也可以叫做缓存读写策略,是指在写数据或读数据时,数据库与缓存之间如何去维护数据一致性的方案。

换一个视角,从CAP理论视角来看,缓存属于AP系统,即保证分区容错和高可用,一致性采用最终一致性策略,所以,对于追求绝对一致性的场景,不适合引入缓存。

CAP理论:简单来说,一个分布式系统只能同时满足AP或者CP,不存在CAP系统,CA系统只存在于单机环境

  • Consistency:强一致性,任何节点在任何时间返回的数据相同
  • Availability:高可用,所有请求能够在有限时间内被响应,即使某些节点故障,依然能够返回有效数据(不一定是最新数据)
  • Partition Tolerance:分区容错性,发生网络分区后能够继续提供服务

三大策略

旁路缓存模式

写场景

  • 更新DB
  • 删除cache

image

策略 一致性风险 推荐度
先更新缓存,再更新数据库 ⚠️ 高风险:缓存更新成功,数据库更新失败 → 缓存新值,数据库旧值 ❌ 不推荐
先更新数据库,再更新缓存 ⚠️ 高风险:数据库更新成功,缓存更新失败 → 数据库新值,缓存旧值 ❌ 不推荐
先删除缓存,再更新数据库 ⚠️ 高并发下风险高:读请求可能读到旧值并写入缓存 ❌ 不推荐
先更新数据库,再删除缓存 ⚠️ 风险低:理论上存在不一致,但概率极小 ✅ 推荐

为什么先更新DB,再删除Cache? 先删除Cache,再更新DB可以吗,当然不可以,数据一致性风险很高,考虑下边这个场景:

  1. 客户端A发起写请求,删除Cache
  2. 客户端B发起读请求,发现Cache不在,查询DB并写入缓存(缓存旧值)
  3. 客户端A写DB(DB新值)

所以,采用先删后写的策略,那先删后写一定可以保证数据一致性嘛?不能够完全保证,但是!出现一致性问题的概率很低

  1. 客户端A发起读请求,没有命中缓存,查询DB
  2. 客户端B发起写请求,没有命中缓存,写DB(DB新值)
  3. 客户端A将查询到的数据写缓存(缓存旧值)

以上场景会出现数据一致性问题,但是为什么说概率很小呢?

  • 两个请求需要命中同一份数据且数据在缓存中不存在
  • 13执行时间远小于2执行时间,但是13是读DB+写缓存,2是写DB,缓存写的时间要远小于硬盘写,前者纳秒级,后者毫秒级

那这么说的话,为什么是删缓存而不是直接写缓存呢?删除操作执行速度更快,在多写情况下,频繁更新容易出现瓶颈

读场景

  • 读Cache,未命中读DB
  • 数据加载到Cache
    image

旁路缓存模式下,DB和Cache操作是同步的,因为需要保证缓存写和DB写的顺序一致,因此旁路缓存不适合多写场景,而且频繁的删除会影响缓存命中率;此外,首次请求的数据一定不在Cache中

有杠精要问了,那我一定要先删缓存再写DB呢?怎么办?延时双删!!!!
先说一句,如果是强一致场景,必须先写DB,再删缓存,先删再写适合于能容忍一定程度不一致的场景,延时双删是指删除缓存之后,间隔一小段时间再次进行删除,中间的这段间隔存在数据不一致风险,因此这个间隔需要尽可能地小,但同时也要保证间隔时间大于写DB的时间

读写穿透

读写穿透和旁路缓存一样,区别只在于将操作DB的任务交给了缓存去做,不需要RD去操作DB,完全面向缓存

写场景

image

读场景

image

异步写入

之前也说了,旁路缓存和读写穿透DB和缓存属于同步操作,在高并发写场景下性能极差,异步缓存写入思想很简单,就是同步更新缓存,异步写DB,但是这种方案的一致性风险很高,因此需要额外的机制保证异步写DB一定成功,分布式系统主从同步、MySQL的Buffer Pool刷盘都用到了异步写入

删除缓存重试机制

上边说的三种一致性策略,无论是删除还是写入,其实都存在由于各种各样原因导致失败的情况,比如删除缓存失败,一旦失败,除非后续有新的写请求,否则缓存中的一直是旧值,可以通过缓存删除重试来解决这个问题。

flowchart TD A[应用服务: 更新数据库] --> B[应用服务: 尝试删除缓存] B --> C{删除是否成功?} C -- 是 --> D[流程结束] C -- 否/异常 --> E[应用服务: 发送删除消息<br>到消息队列] E --> F[消息队列<br>持久化存储消息] F --> G[重试消费者: 拉取消息] G --> H[重试消费者: 尝试删除缓存] H --> I{删除是否成功?} I -- 是 --> J[重试消费者: 确认消费<br>消息从队列移除] I -- 否 --> K[重试消费者: 消息处理失败] K --> L{重试次数<br>超过上限?} L -- 否 --> M[消息队列: 重新投递消息<br>等待下一次重试] L -- 是 --> N[重试消费者: 放入死信队列<br>并告警人工介入] M --> G

一种无侵入式一致性解决方案

通过监听MySQL binlog,自动触发缓存删除,本质上属于异步写入策略,但是其对业务代码没有侵入性,高并发性能很好

posted @ 2025-11-15 18:30  xxs不是小学生  阅读(1)  评论(0)    收藏  举报