高并发性能YUEXIAN INSIGHTS

缓存与数据库不一致,先保住权威版本,再修失效与回填竞态

面向高并发系统研发、架构与运维负责人,说明缓存和数据库不一致的定位方法、写后失效、版本化防旧值回填、可靠变更事件、对账修复及上线验收。

本文目录
先看结论

可直接引用的结论:TTL 只能限制旧值寿命,不能证明缓存与数据库一致

本文面向使用本地缓存、Redis 或其他共享缓存承接高并发读取的研发、架构与运维负责人。缓存通常应是可丢弃的派生副本,数据库或明确的权威存储保留业务事实主责。修复不一致时,先为每类数据定义允许陈旧时间和禁止缓存的关键判断,再把“数据库提交、缓存失效、并发回填、事件补偿、对账修复”拆成可观察状态。写后删除能简化主链路,但删除失败、读写竞态、只读副本落后和多级缓存仍可能让旧值回来;因此还要携带单调业务版本、使用可重放的失效依据,并定期从权威侧校验。验收目标不是宣称绝对强一致,而是证明旧版本不会覆盖新版本、异常能在约定窗口内收敛,并且修复流量不会压垮数据库。

先按业务正确性选择缓存边界

数据类型建议策略主要风险
权限、余额与库存决策最终判断读取权威存储或经过证明的一致性路径,缓存只作提示或受控加速旧权限、旧余额或旧库存参与提交,造成越权或错误受理
商品、配置与资料展示写库成功后失效缓存,按业务版本回填,并设置与陈旧预算匹配的 TTL删除失败或旧读晚到,把更新前内容重新写回缓存
列表、聚合与搜索结果记录依赖范围或使用命名空间版本,允许异步重建并显示数据截止时间只删除单条对象键,列表、计数和搜索副本仍保留旧结果
高频热点读取合并回源、分散过期、限制重建并发,并在恢复时分批预热批量清空虽然去掉旧值,却把一致性事故放大为数据库过载
01

适用边界:先定义权威来源、陈旧预算和禁止缓存的数据

缓存一致性不是一个开关。商品标题短暂陈旧与权限撤销仍被放行的后果完全不同;同一对象的详情、列表、计数、搜索索引和本地缓存也可能有不同更新窗口。开始修复前要先写清哪个存储拥有最终事实、哪些读取允许落后多久,以及超出预算时返回什么。

Microsoft 的 Cache-Aside 官方说明明确指出,该模式不保证缓存和数据存储始终一致;外部进程更新数据库后,旧缓存可能持续到再次加载。因而不能用“项目采用 Redis”代替业务一致性定义,也不能把缓存副本反向当作修复数据库的依据。

权限撤销、余额、库存、配额和状态机提交等关键决策,如果错误后果不可接受,应绕过普通缓存或使用经过单独证明的权威判断路径。展示型、可重算且允许短暂陈旧的数据才适合通过 TTL、失效事件和异步修复取得性能与新鲜度平衡。

  • 为每类缓存记录权威来源、键规则、派生关系、允许陈旧时间和故障返回。
  • 盘点进程内缓存、边缘缓存、共享缓存、只读副本与搜索索引,避免只修一层。
  • 明确缓存丢失、不可用或全量清空时,业务怎样降级且不改变事实主责。
02

先取证再清理:从一条错误响应还原完整版本链

不要发现旧值就先清空全部缓存。应选取脱敏业务样本,沿请求链记录路由、应用版本、缓存层级、规范化键、命中状态、缓存值版本、数据库版本、写入事务、失效记录与时间线,判断旧值来自未失效、失效失败还是失效后又被回填。

常见竞态是:读请求先从数据库取得旧版本,写请求随后提交新版本并删除缓存,先前的读请求最后才把旧版本写回缓存。另一类问题来自回填读取了落后的只读副本;即使删除顺序正确,也可能重新放入旧值。本地缓存未收到失效、键前缀或租户维度错误、列表键漏删、失败重试使用旧负载,也会表现为“Redis 偶发不一致”。

观测字段保持有界:按缓存族、操作、结果、版本比较和错误类别统计命中、回源、失效与修复,不把用户 ID、订单号或完整缓存键放进指标标签。具体样本通过受控日志或追踪关联,敏感值做掩码。先确认错误范围和持续时间,再决定定点删除、版本隔离还是批量重建。

  • 用同一业务样本比较权威版本、共享缓存版本、本地缓存版本与响应版本。
  • 区分删除未执行、删除失败、旧值晚到、读副本落后、键遗漏和序列化兼容问题。
  • 记录事故期间的回源量与热点分布,为修复限速和数据库保护提供依据。
03

写入主链路:先提交权威事实,再失效所有可推导缓存

对典型 Cache-Aside 路径,业务写入先在权威数据库完成事务,提交成功后删除或使相关缓存失效;下一次读取再从权威侧加载。直接先删缓存再写数据库,会在数据库写入失败或并发读取时留下更难解释的状态。写库和缓存操作通常不在同一事务内,因此删除失败必须成为可补偿状态。

失效清单不能只包含对象详情键。更新商品可能影响按分类列表、门店库存摘要、价格聚合、搜索结果和进程内缓存;需要为每个写命令维护可审查的派生关系。依赖集合稳定且较小时可精确删除,组合数量巨大时可使用命名空间或查询版本切换,使旧键自然退出,而不是同步枚举所有组合。

把缓存更新和数据库更新包装成“看起来原子”的双写,并不会自动消除部分失败。若业务确实需要读后写立即可见,可在有限会话内读权威存储、返回已提交版本供后续请求校验,或采用已验证的写穿路径;仍要定义缓存故障时数据库事务是否应失败,不能把较低可用性的缓存意外变成业务提交前提。

  • 数据库事务成功是业务完成的依据,缓存失效失败单独记录并重试。
  • 每个写命令都有对象键、列表键、聚合键、本地缓存和搜索派生的影响清单。
  • 批量更新使用批次版本或可恢复任务,不在请求内无界扫描和删除键。
04

版本化回填:旧请求可以完成,但不能覆盖更新后的版本

仅靠删除仍挡不住“旧读晚写”。缓存负载应携带来自权威记录的单调版本,例如数据库提交序号、受控更新时间或实体版本;回填时只有候选版本不早于当前可见版本才允许写入。版本比较必须在真正写入缓存的位置原子完成,不能先 GET 比较、再无条件 SET。

版本字段必须由权威写入产生并在每次相关变更时单调推进。客户端时间、不同机器本地时间或只精确到秒的更新时间,可能相同或倒退,不适合直接承担并发顺序。删除也要有版本或墓碑语义,否则旧请求可能在对象删除后再次把对象“复活”;负缓存同样需要短生命周期和版本边界。

如果回源来自只读副本,先验证其版本达到请求要求;达不到时等待受控时间、转读主库、返回处理中或不写缓存。多级缓存采用同一内容版本,并让本地层在发现更高版本、命名空间变化或 TTL 到期时失效。不要只给键名加部署版本,因为代码版本与业务数据版本解决的是不同问题。

  • 缓存值包含可解释的业务版本、生成来源和必要的过期信息。
  • 条件回填、删除墓碑与命名空间切换在并发位置原子执行。
  • 旧客户端、旧序列化结构和未知版本不会静默覆盖新值。
05

可靠补偿:变更事件从数据库事实产生,并且可以重放

当写入口很多、派生键复杂或缓存层级较多,可在数据库事务中同时写入变更记录或事务发件箱,再由消费者失效缓存。事件至少包含稳定 eventId、实体类型与 ID、提交版本、变更范围和发生原因;消费者按 eventId 去重,并按实体版本忽略迟到旧事件。

Debezium 的官方 Outbox Event Router 文档说明,事件 ID 可用于识别重复消息,aggregate ID 作为消息键有助于同一聚合在分区内保持顺序。采用其他 CDC 或消息平台时也要按其实际投递、顺序和重放语义验证,不能因为用了消息队列就默认“只发生一次”。失效消费位点、失败重试、死信和全量重放都需要可观察。

Redis 官方资料指出键空间通知建立在 Pub/Sub 上,订阅方断线期间的事件会丢失;因此它适合观察 Redis 自身键变化,不适合作为数据库业务变更的唯一可靠来源。可靠补偿应从权威提交记录产生。若事件系统不可用,主业务可按既定边界继续提交,但未发布事件必须留在可恢复状态,并通过更短 TTL 或关键路径绕过缓存限制风险。

  • 业务变更与待发布事件在同一权威事务边界内落盘。
  • 消费者能处理重复、乱序、暂停、位点丢失、毒消息和历史重放。
  • 事件积压按最老未处理版本和受影响缓存族告警,而不是只看消息总数。
06

对账与修复:从权威侧证明收敛,不能让巡检制造回源风暴

在线修复先选定缓存族和版本水位,从数据库或变更日志得到权威样本,再比较缓存的存在性、版本和必要字段摘要。发现缓存版本落后时优先定点删除或写入更高版本;发现缓存版本异常超前、无法解析或租户错误时隔离样本并调查,不把缓存内容覆盖回数据库。

对账可以按最近变更、关键对象、头部热点和随机样本分层执行。全量扫描应分页、限速、可断点续跑,并服从数据库连接、I/O 与复制延迟预算。列表和聚合不能只比较条目数量,还要核对数据截止版本、过滤条件、租户范围和删除项;否则数量相同也可能包含错误成员。

修复与预热分开。删除旧值后会增加回源,如果同时大批修复热点键,数据库可能在一致性恢复过程中被击穿。使用单飞或请求合并、分散 TTL、每缓存族并发上限和熔断策略,先确保权威查询可用;必要时让非关键读取明确显示旧数据时间或暂时降级,而不是无界等待。

  • 对账任务有水位、范围、速率、断点、差异分类和修复审计。
  • 任何修复都保持数据库权威,不从未知缓存反向覆盖业务事实。
  • 修复、回源与预热共享数据库容量预算,热点键有请求合并和失败退避。
07

灰度与验收:证明异常会收敛,也证明恢复不会压垮数据库

先在一个低风险缓存族启用版本负载、失效记录和对账,只观察差异;确认序列化、键空间和监控稳定后,再开启条件回填与自动修复。按租户、数据类型或路由批次扩大,保留绕过缓存、停止补偿消费者和回退到旧读路径的独立开关。

故障演练覆盖数据库提交后删除超时、消费者停机、重复乱序事件、读副本落后、旧请求晚回填、对象删除后复活、本地缓存未失效、批量更新、缓存重启、热点键同时过期和修复任务中断。逐项核对最终版本、收敛时间、数据库回源、连接池、尾延迟、错误和队列积压,不以平均命中率掩盖关键数据错误。

回滚不能删除版本字段后继续混读新旧负载。应先停止扩大范围,固定受影响命名空间,确保数据库写入不受影响,再按兼容规则切回读取;已经产生的失效事件继续排空或明确封存。主要风险包括版本不单调、派生键漏算、事件长期积压、修复扫描过载、租户键串用、缓存敏感数据泄露,以及把最终一致承诺成强一致。

  • 验收报告包含权威版本、差异类型、收敛窗口、回源峰值与故障时间线。
  • 权限、库存等禁止普通缓存的关键路径通过专项业务断言。
  • 回滚演练证明新旧缓存格式、事件位点和在途修复可安全处置。

需求讨论中的问题

数据库更新后再删除缓存,是否就不会不一致?

仍可能不一致。删除可能失败;更早开始的读请求也可能在删除之后把旧数据库结果写回缓存。需要记录失效失败,并用权威版本和原子条件回填阻止旧值覆盖新值。

把 TTL 调得很短,能替代失效事件和版本吗?

不能。短 TTL 只缩短部分旧值的最长寿命,还会增加回源与热点同时过期风险;在 TTL 窗口内仍可能返回错误版本。TTL 适合作为兜底,实际新鲜度边界仍要结合失效、版本和业务路径。

为什么不能发现不一致就直接清空全部 Redis?

全量清空会让所有请求同时回源,可能把数据正确性问题放大为数据库过载,而且无法解释旧值从哪一层产生。应先确定影响范围,按缓存族或命名空间隔离、定点修复,并控制回源和预热速率。

Redis 键空间通知可以当作可靠的缓存修复队列吗?

不适合作为唯一可靠依据。Redis 官方说明键空间通知使用 Pub/Sub,订阅方断线期间的消息会丢失。数据库变更驱动的失效应来自可恢复的权威提交记录或事务发件箱。

AI 智能体的工具结果和知识查询可以共用普通缓存吗?

只有在租户、用户授权、工具参数、数据版本和时效要求都进入缓存边界时才可评估。涉及个人数据、实时权限或执行结果的响应不应仅按语义相似度复用;模型回答也不能被当作权威业务事实回写。

带走这份清单

缓存与数据库一致性修复上线验收清单

可逐项勾选,辅助本次阅读;刷新页面后不保留。

参考资料

技术概念可进一步阅读以下资料;文中的业务场景与实施清单用于方案讨论。

  1. Microsoft Azure Architecture Center · Cache-Aside pattern

    核验按需加载、写入数据存储后失效缓存、TTL 取舍,以及 Cache-Aside 不保证缓存与数据存储始终一致的适用边界。

  2. Microsoft Azure Architecture Center · Caching guidance

    核验缓存是可丢弃副本、写后失效与写穿的取舍、过期策略,以及最终一致数据源可能把旧值重新填入缓存的风险。

  3. Redis Docs · Keyspace notifications

    核验 Redis 键空间通知使用 Pub/Sub、订阅方断线期间事件会丢失,以及过期事件在键实际删除时产生而非保证在 TTL 到点瞬间产生。

  4. Debezium Documentation · Outbox Event Router

    核验事件 ID 用于识别重复、aggregate ID 作为消息键支持同一聚合的分区顺序,以及事务发件箱事件结构的实现边界。

#缓存一致性#版本控制#失效补偿#数据对账
从阅读,到行动

需要评估现有系统的性能瓶颈?

提供关键业务路径、真实峰值、服务器与数据库配置,以及现有压测或故障记录,月弦科技可据此梳理优化顺序、实施范围和可验证的验收条件。