Skip to content

✅ 为什么 Redis + Lua 脚本需要“最终一致性补偿”?

因为 Redis 是内存数据库,不具备强一致的持久化能力,而且你在 Redis 中“扣减库存”只是缓存层面的扣减,后续你仍然需要把这笔扣减写入到数据库(MySQL 等)中。

👉 经典秒杀流程:

  1. 用户点击秒杀商品
  2. Redis 中执行 Lua 脚本,判断库存 > 0,原子扣减
  3. 发送异步消息(MQ)或走异步线程落地订单到数据库

💣 问题出现了:

如果 Redis 扣减成功了,但【后续落库失败了】(例如服务宕机、MQ投递失败、数据库挂了),那:

  • Redis 的库存已经扣掉了
  • 数据库里却没有这笔订单

👉 就会产生库存数据不一致的问题(即“幻影库存”)

这就叫做:“扣了 Redis 没落库,数据不一致”。

🔧 怎么实现“最终一致性补偿”?

为了保障 Redis 和数据库之间最终一致,你要设计一个补偿机制,核心目标是:

确保所有扣了 Redis 的请求,最终一定能落地数据库,或者把 Redis 回滚”。

✅ 常见方案一:MQ事务保障 + 补偿队列

  1. 扣减库存(Redis+Lua)
  2. 发送消息到 MQ(Kafka, RocketMQ)
  3. MQ 消费者落库
  4. 失败的消息进入补偿队列 / 死信队列
  5. 补偿线程定时扫描失败消息,重试落库 or 回滚 Redis

✅ 常见方案二:本地消息表 + 定时补偿

  1. 扣减 Redis 库存
  2. 写入本地消息表一条“订单待落库”记录(事务内)
  3. 异步落库订单
  4. 成功就把消息表标记为已处理
  5. 定时任务扫描未处理的记录,进行补偿

✅ 常见方案三:Redis 写入临时日志 + 后置对账

  1. Lua 脚本除了扣减库存,还写入一条 Redis 侧“扣减日志”

    lua
    redis.call("decr", "stock:123")
    redis.call("lpush", "log:stock", orderId)
  2. 后台定时消费这些日志,做数据落地

  3. 检查是否订单落库成功,没成功就补偿(重新发起或回滚)

🔚 总结

方案特点
Redis + Lua 脚本原子操作,抗并发
但不落库就可能“扣了库存却没生成订单”
所以需要补偿机制MQ+重试、消息表+任务、Redis日志+对账

🚀 实战方方案

  • Redis+Lua做库存原子扣减
  • Kafka / RabbitMQ 异步落库
  • 本地消息表 or 补偿任务保证一致性

所有文章版权皆归博主所有,仅供学习参考。