1. 缓存一致性概述

1.1 缓存一致性问题的定义

在分布式系统中,缓存一致性(Cache Consistency)是指缓存层与数据源(如数据库、远程服务)之间数据状态的对等程度。当缓存中存储的数据与源端数据在任何时刻都保持相同,则称系统达到了强缓存一致性;若允许短暂或最终对齐,则属于最终一致性模型。

缓存一致性问题的本质是:同一份数据在多个存储副本(缓存副本 + 数据库副本)之间出现了值分歧。这种分歧一旦被业务请求读取到,就会导致脏读、重复提交、库存超卖、配置错乱等严重故障。

核心定义:缓存一致性 = 缓存中的数据与源端数据在业务可接受的时间窗口内保持逻辑等价。

1.2 产生原因:写后读与删后读

缓存一致性问题主要由两类操作时序冲突引发:写后读(Write-Then-Read) 和 删后读(Delete-Then-Read)。下表对比了两种场景的触发条件与典型后果:

冲突类型 操作时序 典型场景 后果
写后读 线程A更新数据库 → 线程B读取缓存(旧值) 用户修改个人信息后刷新页面仍看到旧资料 脏读、数据不一致
删后读 线程A删除缓存 → 线程B并发写入旧缓存 → 线程C读取到旧值 商品库存扣减后仍显示原库存 缓存与数据库长期不一致

写后读(Write-Then-Read)

写后读是最常见的一致性破坏路径。流程如下:

  1. 业务线程先更新数据库(写操作)。
  2. 缓存未及时失效或更新。
  3. 另一个线程读取缓存,拿到的是更新前的旧数据。

这种问题在先写库、后删缓存的策略中尤为突出——如果删除缓存失败或延迟,后续读请求就会命中旧缓存。

删后读(Delete-Then-Read)

删后读的破坏性更强,因为它涉及并发写入的竞态条件:

  1. 线程A删除缓存键。
  2. 线程B(读请求)发现缓存缺失,回源数据库读取旧数据,并将旧数据写回缓存。
  3. 线程A此时才更新数据库(或更新操作在步骤1之前已完成但尚未生效)。
  4. 缓存中永久保留了旧数据,数据库已更新,二者长期不一致。
注意:删后读问题在高并发场景下极易触发,是缓存穿透保护失效的常见诱因之一。即使采用“延迟双删”策略,也无法完全消除时间窗口内的不一致风险。
缓存一致性冲突产生路径 写后读冲突 线程A:更新DB 线程B:读缓存 时间 DB已更新 缓存未失效 结果:DB新值 结果:缓存旧值 → 脏读:读到旧数据 删后读冲突 线程A:删缓存 线程B:读DB 缓存已删除 线程B写回旧值 DB已更新 缓存永久旧值 → 长期不一致

1.3 对系统稳定性的影响

缓存一致性问题的破坏力不仅体现在数据层面,还会逐级放大为系统稳定性风险。以下是主要影响维度:

影响维度 具体表现 严重等级
数据正确性 用户看到过期数据、库存超卖、订单状态错乱 P0(致命)
缓存穿透放大 不一致导致缓存频繁失效,大量请求穿透到数据库 P1(严重)
数据库负载飙升 穿透流量打垮数据库,引发级联故障 P1(严重)
业务逻辑紊乱 基于缓存做判断的限流、降级、风控策略失效 P2(中等)
运维排障困难 不一致问题间歇性复现,难以定位根因 P2(中等)
经验法则:缓存一致性问题的治理优先级应高于缓存命中率优化。一个命中率99%但存在数据不一致的缓存系统,比一个命中率90%但数据绝对一致的系统更危险。

1.4 一致性模型分类

根据业务对一致性的容忍度,缓存一致性模型可分为以下三类:

  • 强一致性(Strong Consistency):写操作完成后,任何后续读操作都能读到最新值。实现代价高,通常需要分布式锁或同步失效,适用于金融、库存等场景。
  • 最终一致性(Eventual Consistency):允许短暂的不一致窗口,但保证在无新写入后,所有副本最终达到一致。适用于社交动态、内容推荐等场景。
  • 弱一致性(Weak Consistency):不保证读操作能拿到最新值,甚至不保证最终一致。适用于日志、监控等非关键数据。

在实际工程中,大多数业务系统采用最终一致性 + 补偿机制的组合策略,在性能与正确性之间取得平衡。

关键认知:缓存一致性不是“全有或全无”的二元选择,而是一个可调优的连续谱。通过合理的OpenAPI契约设计,可以在不同接口上声明不同的一致性等级,实现精细化的治理。