✅ 为什么 Redis + Lua 脚本需要“最终一致性补偿”?
因为 Redis 是内存数据库,不具备强一致的持久化能力,而且你在 Redis 中“扣减库存”只是缓存层面的扣减,后续你仍然需要把这笔扣减写入到数据库(MySQL 等)中。
👉 经典秒杀流程:
- 用户点击秒杀商品
- Redis 中执行 Lua 脚本,判断库存 > 0,原子扣减
- 发送异步消息(MQ)或走异步线程落地订单到数据库
💣 问题出现了:
如果 Redis 扣减成功了,但【后续落库失败了】(例如服务宕机、MQ投递失败、数据库挂了),那:
- Redis 的库存已经扣掉了
- 数据库里却没有这笔订单
👉 就会产生库存数据不一致的问题(即“幻影库存”)。
这就叫做:“扣了 Redis 没落库,数据不一致”。
🔧 怎么实现“最终一致性补偿”?
为了保障 Redis 和数据库之间最终一致,你要设计一个补偿机制,核心目标是:
“确保所有扣了 Redis 的请求,最终一定能落地数据库,或者把 Redis 回滚”。
✅ 常见方案一:MQ事务保障 + 补偿队列
- 扣减库存(Redis+Lua)
- 发送消息到 MQ(Kafka, RocketMQ)
- MQ 消费者落库
- 失败的消息进入补偿队列 / 死信队列
- 补偿线程定时扫描失败消息,重试落库 or 回滚 Redis
✅ 常见方案二:本地消息表 + 定时补偿
- 扣减 Redis 库存
- 写入本地消息表一条“订单待落库”记录(事务内)
- 异步落库订单
- 成功就把消息表标记为已处理
- 定时任务扫描未处理的记录,进行补偿
✅ 常见方案三:Redis 写入临时日志 + 后置对账
Lua 脚本除了扣减库存,还写入一条 Redis 侧“扣减日志”
luaredis.call("decr", "stock:123") redis.call("lpush", "log:stock", orderId)
后台定时消费这些日志,做数据落地
检查是否订单落库成功,没成功就补偿(重新发起或回滚)
🔚 总结
方案 | 特点 |
---|---|
Redis + Lua 脚本 | 原子操作,抗并发 |
但不落库 | 就可能“扣了库存却没生成订单” |
所以需要补偿机制 | MQ+重试、消息表+任务、Redis日志+对账 |
🚀 实战方方案
- Redis+Lua做库存原子扣减
- Kafka / RabbitMQ 异步落库
- 本地消息表 or 补偿任务保证一致性