第243集MySQL与Redis数据一致性架构实战:强一致、最终一致与高可用落地指南
前言
数据库与缓存双写(或读写分离)极大提升了系统吞吐,但也带来了臭名昭著的“数据不一致”问题:脏读、过期数据、穿透、回源风暴、并发覆盖等。在高并发、复杂场景(订单、库存、账户、计费、风控)下,如何在性能与一致性之间取得最优平衡,是架构师必须掌握的核心能力。
本文从一致性模型、写读路径、工程化手段与失败恢复四个维度,分场景给出“强一致”和“最终一致”两大落地路线,并提供架构图、关键代码与上线检查清单。
一、一致性模型与约束
- 强一致:写成功后,任何读都必须返回最新值。适用于资金、安全、强事务场景。
- 因果/会话一致:同一会话内读到自己写。适用于用户偏好、草稿等弱事务业务。
- 最终一致:在受控时间内收敛到一致。适用于大多数读多写少、可容忍短暂过期的业务。
一致性目标需与性能(P99延迟)、可用性(SLA)、成本(链路复杂度)共同评估,按域划分:强一致域(核心交易)与最终一致域(报告、榜单、聚合)。
二、常见架构模式与读写时序
核心抉择点:
- 写时同步更新缓存(强一致)还是异步更新(最终一致)。
- 读未命中是否需要读穿数据库并回填缓存,是否要加“重建保护”(互斥/单飞)。
- 删除缓存 vs 更新缓存:更新粒度大、成本高时更倾向删除;简单KV则更新。
三、方案选型对比(写路径)
| 方案 | 一致性 | 吞吐 | 复杂度 | 典型场景 |
|---|---|---|---|---|
| DB写成功后同步回写Redis | 高(近强一致) | 中 | 中 | 核心交易的热点配置、账户快照 |
| 延迟双删(写前del、写后延迟del) | 中 | 高 | 低 | 读多写少、短暂过期可接受 |
| Outbox + 可靠消息(同库事务) | 最终一致(可证明) | 高 | 中高 | 订单、库存、积分等异步扩散 |
| Binlog订阅(Canal/Flink CDC) | 最终一致 | 很高 | 中高 | 老系统改造、跨语言多消费者 |
| 逻辑版本/CAS + Lua原子更新 | 高 | 高 | 中 | 排他写、并发覆盖风险场景 |
关键结论:
- “写DB+删缓存”优于“写DB+更新缓存”在高并发下的冲突概率,但需防止删失败/延迟窗口内脏读,常配合延迟二次删除或消息对账。
- Outbox 是“可验证”的最终一致基线方案,优先级高于“最佳努力通知”。
- Binlog 订阅适合异构/历史系统,但注意延迟与重放幂等。
四、强一致落地方案(事务读穿 + 回写)
特征:写路径同步更新缓存;读路径命中即返回,未命中则直读DB并回写;热点键采用互斥重建。
4.1 时序与关键点
- 事务提交后再回写缓存,避免脏写;缓存设置合理TTL并带版本号/时间戳字段。
- 读未命中时通过分布式互斥(如 Lua+SET NX PX)保护缓存重建,防击穿。
- 写多副本场景用逻辑版本(version)防覆盖;读取携带版本以便写失败回滚。
4.2 Lua 互斥重建脚本(简化示例)
1 | -- 获取互斥锁并设置过期,避免重建风暴 |
4.3 Java 伪代码
1 | // 写:DB 成功 -> 回写 Redis |
适用:强事务域、风控阈值、价格/限购等需强一致的数据。
五、最终一致落地方案(Outbox/CDC 驱动)
5.1 Outbox + 可靠消息
- 在同一本地事务内:更新业务表 + 写入 outbox 事件表。
- 事件轮询器可靠投递到 MQ;消费者按事件重建/删除缓存。
- 幂等键:事件ID或业务ID+版本,避免重复投递/消费。
1 | -- 事件表示例 |
1 |
|
5.2 Binlog 订阅(Canal/Flink CDC)
- 无需改动业务写路径;通过订阅 binlog 触发缓存更新/删除。
- 需做好 schema 演进、重放窗口控制与幂等消费。
适用:系统耦合度高、难以改写;跨系统数据汇聚、搜索构建、报表聚合。
六、读路径优化与缓存治理
- 缓存穿透:对不存在数据反复回源。治理:布隆过滤器、空值短TTL、接口校验。
- 缓存击穿:热点Key过期瞬间高并发重建。治理:互斥锁/单飞、逻辑过期+后台异步刷新。
- 缓存雪崩:大量Key同一时间过期。治理:TTL随机抖动、分区过期、热Key预热。
- 热点Key保护:读多写少数据使用“读写分离+逻辑过期”降低写放大。
七、并发控制与幂等设计
- 逻辑版本/CAS:更新条件携带版本,防覆盖。
- 幂等键:消息ID、业务ID+版本,落表/缓存去重。
- 原子性:使用 Lua 构建原子读改写;避免多次网络往返。
1 | -- Lua 原子更新带版本 |
八、失败场景与恢复策略
- 写DB成功但删缓存失败:重试队列/补偿任务;Outbox对账。
- MQ丢失或重复:开启持久化、消费幂等、死信重投、最大重试次数与人工干预通道。
- 重建风暴:限流/舱壁;热点Key隔离;异步预热;逻辑过期后台刷新。
- 版本漂移:写入时携带版本并校验;失败重试回读最新状态。
九、性能与稳定性评估指标
- 写入P99延迟、读命中率、回源比例、重建耗时、互斥锁冲突率。
- 事件积压时长、死信数、幂等冲突率。
- 缓存体积、热点分布、过期抖动曲线。
十、实施清单(上线前 Checklist)
- 明确一致性域:强一致域与最终一致域名单与边界。
- 选型基线:强一致用“事务读穿+回写”;最终一致优先 Outbox。
- 幂等策略:消息ID/业务ID+版本,重复消费零副作用。
- 缓存治理:TTL随机抖动、热点互斥、布隆过滤器、逻辑过期策略。
- 监控告警:命中率、回源率、重建耗时、事件积压、死信、锁冲突。
- 压测基线:热点与均匀双场景;升压到1.5倍峰值;观察P99与抖动。
- 故障演练:MQ停机、Redis重启、DB主从切换、热点突发等演练剧本。
十一、典型落地组合
- 强一致:核心交易域采用“直读DB+回写缓存+互斥重建+版本控制”。
- 最终一致:订单/库存采用“Outbox + 可靠消息 + 幂等消费者 + 删缓存”。
- 老系统改造:Canal 订阅 binlog 驱动缓存/索引更新,配幂等与重放窗口。
十二、FAQ
- Q:写DB、删缓存谁先?
- A:通常“先写DB,后删缓存”,失败补偿与延迟二次删除,避免先删导致短暂脏读窗口变大。
- Q:更新还是删除缓存?
- A:更新粒度大/复杂度高时删更稳;简单KV、强一致域可以直接更新。
- Q:如何防止热点Key击穿?
- A:互斥锁、逻辑过期、后台刷新、热点隔离与限流配合。
本文为架构师级别的实践手册,建议结合业务分域选择基线方案:强一致以“读穿+回写”为主,最终一致以“Outbox/CDC”为主;在此之上,以幂等、互斥、限流与监控为安全网,获得性能与一致性的最优解。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 1024bibi.com!
评论


