Skip to content

Latest commit

 

History

History
189 lines (119 loc) · 4.29 KB

File metadata and controls

189 lines (119 loc) · 4.29 KB

对一个电商平台来说突然的秒杀场景,如何进行库存扣减。

一、秒杀库存扣减的核心挑战

秒杀的特点是:

  1. 高并发 同一时间有几十万、上百万请求同时抢一个库存。

  2. 库存极少 比如 1000 件。

  3. 不能超卖 超卖导致重大售后问题。

  4. 业务链路必须极短 扣库存必须快(毫秒级)。

  5. 数据库承压不能太大 秒杀流量不能把主站压垮。

所以目标是:

  • 扣减要 原子性
  • 避免数据库被打爆
  • 不允许超卖
  • 能回溯成功/失败
  • 撤回和放库存也要安全

二、常见的扣库存方式(由弱到强)

方式 1:直接 MySQL 扣减(悲观锁 / 乐观锁)

update product set stock = stock - 1 where id = 1 and stock > 0;

优点:

  • 简单
  • 强一致性

缺点:

  • QPS 极低(几百)
  • 秒杀瞬间直接把库打挂
  • 不适合真实秒杀场景

不能用于真正秒杀,只能用于普通下单。


三、真正用于秒杀的库存扣减方案(大厂通用)

秒杀场景一般采用 “削峰 + 前置判断 + 内存扣减 + 异步确认” 的架构。

下面给最佳方案。


四、最佳方案:Redis + Lua 原子扣减(强烈建议)

Redis 的特性:

  • 单线程 → 天然原子性
  • 内存 → 高吞吐
  • 支持 Lua → 业务逻辑原子执行

扣库存 Lua 脚本示例(面试官很爱看)

local stock = tonumber(redis.call("GET", KEYS[1]))
if stock <= 0 then
    return -1  -- 库存不足
end
redis.call("DECR", KEYS[1])
return 1  -- 扣减成功

Go/Java 直接执行 Lua,每秒可承受 10~20 万 QPS。

优点

  • 扣减是原子的,不会超卖
  • Redis 是内存级别,性能爆炸强
  • 所有请求不打数据库

缺点

  • Redis 异常会影响库存一致性,需要持久化机制(后面讲)

五、核心思想:库存“前置”到 Redis,数据库做最终确认

流程图:

用户请求 → 接入层限流
        → Redis 预扣库存(Lua 原子扣减)
        → 写入秒杀订单消息队列
        → 消费 MQ,在数据库最终落地订单、扣库存

六、为什么要加消息队列(MQ)?

如果 Redis 扣库存成功,用户不会立即下订单,而是将消息写入 Kafka / RocketMQ:

  • 订单写数据库是慢操作
  • 秒杀并发巨大
  • 直接写数据库必挂
  • MQ 能承受百万级写入做削峰

最终由订单服务异步消费消息:

  • 检查用户资格
  • 建订单
  • 持久化最终库存(这里使用 MySQL 扣减)

这样设计有三个目的:

  1. 削峰
  2. 数据库只处理异步订单,压力小
  3. 确保订单按序落地,可回查可补偿

七、解决 Redis 和 MySQL 的一致性问题

当 Redis 扣库存成功,但数据库订单没写成功怎么办?

解决方案:

方式 1:Redis 中设置“占位的库存锁”

扣库存时记录:

stock_reserved = +1

订单失败时,补偿库存:

stock += 1

方式 2:订单写失败发送补偿消息

MQ 有死信队列,订单失败会触发“归还库存消息”。

方式 3:定时校准库存

每天使用定时任务扫描数据库与 Redis 差异,修复异常。


八、为了防止 Redis 仍然成为热点,需要进一步优化

技术 1:接入层限流(Nginx、Envoy、Gateway)

根据商品库存量,比如 1000 件,整个系统最多放行 10 倍请求(1 万),剩下都在网关拒绝。

技术 2:分布式令牌桶 + 秒杀资格检查

不让所有请求打到库存服务。

技术 3:本地内存标记已售空

服务收到库存为 0,即刻标记 sold_out = true,直接拒绝后续请求


九、总结

秒杀场景下库存扣减不能直接打数据库,需要使用 “Redis + Lua 扣库存 + MQ 下单 + MySQL 最终落库” 的异步架构。

具体流程: 1)流量通过网关限流、削峰 2)Redis Lua 原子扣减库存,保证不超卖 3)扣减成功的请求写入消息队列 4)订单服务异步消费 MQ,写 MySQL,完成真正的扣库存 5)通过补偿消息、定时任务确保 Redis 和数据库一致性

这样可以轻松支撑几十万到上百万 QPS,同时保证库存严格正确。