综合题:一次下单链路里 Redis、MQ、MySQL 如何协作?可靠性与一致性如何取舍?
结论先行:下单是“强一致核心写 + 最终一致周边”的典型场景:Redis 扛读与瞬时写(缓存商品、库存预扣、防重、限流),MySQL 用事务保证订单与库存的强一致,MQ 把必须发出去但不必同事务的后续动作(通知、积分、优惠券、对账)异步化。三者协作的命门是 MySQL 提交成功与 MQ 投递成功之间的一致性——用事务消息或本地消息表解决。
一、一次下单链路全景
1. 读商品/价格 → Redis(本地缓存优先),未命中再查 MySQL
2. 幂等与资格校验 → Redis SETNX 防重复下单 + 网关限流
3. 库存预扣 → Redis Lua 原子扣减(先拿资格)
4. 创建订单(主流程) → MySQL 事务:订单表 + 库存最终扣减
5. 触发后续动作 → 本地消息表/事务消息 → MQ
6. 异步消费 → 积分、优惠券、库存回补、通知(各自幂等)
二、各部分职责与取舍
| 组件 | 干什么 | 一致性定位 |
|---|---|---|
| Redis | 缓存读、预扣、幂等、限流 | 短暂状态,丢了可重建/对账 |
| MySQL | 订单/库存落库、事务 | 强一致,最终裁决者 |
| MQ | 削峰、解耦、异步化 | 至少一次投递,消费端幂等 |
三、可靠性的三道闸
- 生产可靠:事务消息(半消息 → 本地事务成功 → 确认投递)或本地消息表 + 定时扫描补偿,保证“订单在、事件必达”。
- 消费可靠:消费者手动 ack,失败进重试队列,超限进死信人工兜底。
- 幂等兜底:所有消费者按业务键去重,容忍 MQ 重复投递。
- 顺序保障:同一订单的事件按业务键路由到同一分区/队列,保证消费顺序;不同订单可并行。
四、一致性的表达框架
- 订单 + 库存必须强一致:同库同事务,行锁防超卖(见第 49 章)。
- Redis 与 MySQL 之间:缓存可短暂不一致,靠删除/过期收敛(见第 40 章)。
- MQ 与 MySQL 之间:最终一致,靠本地消息表/事务消息 + 补偿对账兜底。
- 回答顺序:先画链路 → 再讲每层可靠性 → 最后明确哪里强一致、哪里最终一致。
常见追问与记忆点
- 追问:Redis 预扣了但 MySQL 事务失败怎么办?回补 Redis 库存并记录对账流水,最终以 MySQL 为准做补偿。
- 追问:极端情况下 MQ 真丢了消息怎么办?本地消息表重扫 + 定期对账(订单状态 vs 应发事件)发现缺口补发。
- 记忆点:MySQL 定终态、Redis 扛瞬时、MQ 做解耦;可靠靠事务消息/本地表 + ack + 幂等,取舍是“强一致在库、最终一致在外围”。