4. 缓存一致性问题定义

在分布式系统中引入缓存,本质上是用冗余数据换取访问速度。当缓存中的数据与后端数据库(或源服务)中的数据出现差异时,就产生了缓存一致性问题。理解这一问题的定义、典型场景以及一致性模型,是设计可靠缓存策略的前提。

4.1 什么是缓存一致性

缓存一致性(Cache Consistency)是指:对于同一份数据,缓存中的副本与数据源(如数据库、上游服务)中的主本在逻辑上保持等价。当数据源发生变更时,缓存副本必须在可接受的时间窗口内同步更新或失效,否则系统将返回过时(Stale)或错误的数据。

核心矛盾:缓存层追求高性能(读快、写少阻塞),数据源追求高可靠(持久化、事务性)。两者之间的同步延迟是缓存一致性问题的根源。

4.2 数据不一致的典型场景

在实际的 gRPC 微服务调用链中,以下三种场景最容易引发缓存不一致:

场景 触发条件 后果 典型 gRPC 场景
写后读(Read-After-Write) 客户端写入数据源后,立即从缓存读取 读到旧值(Stale Read) 用户更新个人信息后,其他服务通过 gRPC 查询仍返回旧头像
并发写(Concurrent Write) 多个客户端同时更新同一数据,缓存与数据源更新顺序不一致 数据永久不一致(Lost Update) 库存服务并发扣减,缓存扣减成功但数据库扣减失败
缓存与数据源独立失效 缓存 TTL 过期与数据源更新窗口不匹配 缓存穿透或雪崩 配置中心推送新配置,但 gRPC 服务端缓存未及时刷新

4.2.1 写后读不一致详解

这是最常见的缓存一致性问题。以 gRPC 更新用户信息为例:

  1. 客户端 A 调用 UpdateUser() gRPC 方法,成功更新数据库中的用户昵称。
  2. 缓存中仍保留旧昵称(未主动失效或更新)。
  3. 客户端 B 调用 GetUser() gRPC 方法,缓存命中,返回旧昵称。
  4. 客户端 B 看到的是过时数据,直到缓存 TTL 过期或手动失效。
避坑提示:写后读不一致在“先更新数据库,再删除缓存”策略中尤其容易发生。如果数据库更新成功但缓存删除失败(网络抖动、Redis 宕机),缓存将永久保留旧数据。

4.2.2 并发写不一致详解

当多个 gRPC 请求并发修改同一数据时,缓存与数据库的更新顺序可能交错,导致最终状态不一致。

// 并发写导致缓存与数据库不一致的伪代码示例
// 线程1:扣减库存
cache.decrement("stock:item100", 1);   // 缓存扣减成功,剩余 9
db.execute("UPDATE stock SET count=count-1 WHERE id=100"); // 数据库扣减成功

// 线程2:扣减库存(与线程1几乎同时)
cache.decrement("stock:item100", 1);   // 缓存扣减成功,剩余 8
db.execute("UPDATE stock SET count=count-1 WHERE id=100"); // 数据库扣减成功

// 如果线程2的数据库操作先于线程1提交,最终缓存为8,数据库为9 → 不一致

这种场景在 gRPC 流式处理、批量库存扣减、秒杀系统中尤为突出。

4.3 强一致性与最终一致性

根据系统对一致性的容忍程度,缓存一致性模型分为两个极端:

模型 定义 读延迟 写吞吐 适用场景
强一致性 任何读操作都能读到最近一次写操作的结果 高(需同步等待缓存更新) 低(写操作需同步更新缓存) 金融交易、库存扣减、配置中心
最终一致性 在没有新写操作的情况下,经过一段时间后所有副本最终达到一致 低(直接读缓存) 高(异步更新缓存) 用户资料、文章列表、推荐系统
实践建议:在 gRPC 微服务架构中,90% 的场景采用最终一致性即可满足业务需求。强一致性通常只在“写后立即读”且“读错代价极高”的关键路径上使用,例如支付状态、库存扣减。

4.4 缓存一致性模型对比图

下图展示了强一致性与最终一致性在数据流上的核心差异:

强一致性与最终一致性对比 强一致性 vs 最终一致性 — 数据流对比 强一致性模型 客户端 写请求 缓存 同步写 数据库 同步确认 读请求 读缓存 返回最新数据 写操作同步更新缓存和数据库 读操作保证返回最新写入结果 最终一致性模型 客户端 写请求 缓存 异步写 数据库 读请求 读缓存 可能返回旧数据 写操作先更新缓存,异步同步数据库 读操作可能读到旧值,但最终会一致

4.5 一致性模型选择决策树

在实际的 gRPC 服务设计中,可以根据以下条件快速选择一致性模型:

  • 是否允许短暂读到旧数据?
    • 是 → 使用最终一致性(推荐默认选择)
    • 否 → 进入下一步判断
  • 写后立即读的概率是否极高?
    • 是 → 使用强一致性(如支付回调、库存扣减)
    • 否 → 使用最终一致性 + 缓存预热即可
  • 数据不一致是否会导致资损或安全风险?
    • 是 → 必须使用强一致性,甚至考虑放弃缓存直接读库
    • 否 → 最终一致性 + 监控告警即可
本章小结:缓存一致性问题的本质是缓存副本与数据源主本之间的同步延迟。写后读和并发写是两种最典型的不一致场景。强一致性保证读到的永远是最新数据,但牺牲了写性能和可用性;最终一致性允许短暂的不一致窗口,但换来了更高的吞吐和更低的延迟。在 gRPC 微服务架构中,按业务场景分层选择一致性模型,是构建高性能缓存系统的关键第一步。