答案是:可以,但仅靠简单的 INCR/DECR 指令是不够的,必须配合逻辑判断或 Lua 脚本,并解决"数据库一致性"问题。
使用 Redis 处理库存扣减是高并发场景(如秒杀)中最通用的方案。以下是详细的分析、实现方式以及必须注意的坑。
一、 为什么可以用 Redis?
Redis 的 INCR(自增)、DECR(自减)、INCRBY 等操作是**原子性(Atomic)**的。
这意味着在同一时刻,无论有多少个并发请求,Redis 都会一个个排队执行,不会出现"读取到库存是1,两个线程同时减1,结果还是0"这种竞态条件(Race Condition)。
二、 实现方案
方案 1:直接使用 DECR(最简单,但有小缺陷)
通常库存是减少,所以用 DECR(或者 INCRBY key -1)。
逻辑:
- 初始化库存:
SET stock:1001 10 - 请求进来执行:
DECR stock:1001 - 判断返回值:
- 如果返回值 ≥\ge≥ 0:扣减成功,允许下单。
- 如果返回值 < 0:库存不足,扣减失败。
缺陷:
如果库存是 0,瞬间涌入 1000 个请求,库存会变成 -1000。虽然业务逻辑上你可以判断返回值 < 0 就拒绝,但 Redis 里的数据很难看,且如果后续有"回滚库存"的操作,逻辑会变得复杂(比如你是要加回 1 变成 -999 还是怎样?)。
方案 2:使用 Lua 脚本(推荐方案)
为了避免库存变成负数,并保证"读取库存"和"扣减库存"这两个动作的原子性,推荐使用 Lua 脚本。
Lua 脚本逻辑:
lua
-- keys[1] 是商品库存 key
local key = KEYS[1]
-- 获取当前库存
local stock = tonumber(redis.call('get', key))
-- 判断库存是否充足
if (stock and stock > 0) then
-- 扣减库存
redis.call('decr', key)
return 1 -- 成功
else
return 0 -- 失败
end
优势:
- 原子性: Redis 执行 Lua 脚本时是原子的,中间不会插入其他命令。
- 数据干净: 库存减到 0 后就不会再减了,不会出现负数。
三、 必须解决的核心问题
仅在 Redis 里扣减库存是不够的,你还必须考虑整个交易流程的闭环:
1. 数据库与 Redis 的一致性(最重要)
Redis 只是抗并发的"挡箭牌",最终数据必须落到 MySQL/Oracle 等数据库中。
- 问题: Redis 扣减成功了,但后续代码写数据库(创建订单)失败了,或者服务崩了,怎么办?
- 解决方案:
- 同步写入(简单场景): Redis 扣减成功 -> 开启数据库事务 -> 写入订单 -> 扣减数据库库存 -> 提交事务。如果数据库操作失败,需要Catch异常并回滚 Redis 库存 (
INCR回去)。 - 异步写入(高并发场景): Redis 扣减成功 -> 发送消息到 MQ(RabbitMQ/Kafka/RocketMQ) -> 消费者慢慢消费消息并写入数据库。这能最大程度抗压,但逻辑最复杂(需要处理消息丢失、重复消费等问题)。
- 同步写入(简单场景): Redis 扣减成功 -> 开启数据库事务 -> 写入订单 -> 扣减数据库库存 -> 提交事务。如果数据库操作失败,需要Catch异常并回滚 Redis 库存 (
2. 库存回滚(超时/取消)
用户抢到了库存(Redis 减了),但是:
- 用户一直不付款(超时)。
- 用户主动取消订单。
- 支付失败。
解决方案:
必须有一个机制监听这些事件(如订单超时延迟队列),一旦发生,必须调用 Redis 的 INCR 把库存加回去,否则会导致"少卖"。
3. 缓存预热
秒杀开始前,必须先把数据库里的库存数量同步加载到 Redis 中(预热)。如果 Redis 里没有 key,直接 DECR 会报错或变成 -1。
4. Redis 宕机风险
如果 Redis 挂了,整个秒杀功能就不可用了,且 Redis 里的库存数据可能丢失(取决于持久化策略 AOF/RDB)。
解决方案: 使用 Redis Sentinel 或 Cluster 高可用架构;在代码层面做降级处理(比如 Redis 挂了直接查数据库,但要做好数据库限流)。
四、 总结建议
可以直接用 INCR/DECR 操作吗?
可以,但建议用 Lua 脚本 代替简单的指令。
完整的架构建议:
- 预热: 活动开始前将库存写入 Redis。
- 扣减: 请求到达 -> 运行 Redis Lua 脚本扣减库存。
- 失败 -> 返回"已售罄"。
- 成功 -> 进入下一步。
- 下单:
- 方案 A(强一致): 写入数据库订单,如果失败则回滚 Redis。
- 方案 B(高性能): 发送 MQ 消息,异步创建订单。
- 回滚: 监听未支付订单,超时后触发 Redis
INCR回补库存。
这种方案能抗住上万甚至十万级的 QPS,是业界标准的抗超卖方案。