这个问题是电商面试中的高频题,涉及业务一致性与分布式系统核心技术。下面我将为你撰写一篇结构清晰、技术深入的文章,既适合学习,也适合面试准备。
🛒 《面试官:说说电商库存扣减如何防超卖?分布式锁的三种实现》
“秒杀不到,库存没了;秒杀到了,库存却超了。” —— 这是电商最怕的事故之一。
在电商系统中,库存扣减是核心业务,一旦出错,轻则用户投诉,重则资损赔偿。而高并发场景下,如何防止“超卖”(Over-Selling)?答案离不开:分布式锁 + 原子操作 + 幂等性设计。
本文将深入讲解:
- 超卖是如何发生的?
- 如何设计安全的库存扣减流程?
- 三种主流分布式锁实现方式对比
- 面试加分项:Redisson、ZooKeeper、数据库锁
一、什么是“超卖”?
超卖:实际卖出的商品数量 > 库存数量。
示例场景:
• 商品库存:10 件
• 同时 15 人下单成功 → 超卖 5 件
• 结果:无法发货,引发客诉、退款、平台信誉受损
二、超卖的根本原因
原因 说明
并发读写未加锁 多个线程同时读取库存为 10,各自扣减,最终扣成负数
非原子操作 查询库存 + 判断 + 扣减 三步分离,中间被插队
缓存与DB不一致 Redis 预扣库存,但 DB 更新失败,未回滚
重试机制不当 消息重试导致重复扣减
三、正确库存扣减流程设计
✅ 核心原则:原子性 + 幂等性 + 最终一致性
推荐流程(以秒杀为例):
- 用户点击购买 → 进入排队队列
- 检查库存(Redis 预扣)
- 扣减 Redis 库存(Lua 脚本保证原子性)
- 发送 MQ 消息 → 异步扣减数据库
- 返回“抢购中”状态
- 消费者消费消息 → 扣减 DB 库存
- 扣减成功 → 通知用户付款
- 超时未付款 → 回补库存(Redis + DB)
⚠️ 关键点:所有“读-改-写”操作必须原子化,不能拆开!
四、分布式锁的三种实现方式
问:如何保证同一时间只有一个请求能扣减某商品的库存?
答:用分布式锁,把“扣减”变成串行操作。
方式一:基于 Redis 的分布式锁(最常用)
实现方式:SET key value NX PX 30000
// 加锁
String lockKey = "stock:lock:" + productId;
String requestId = UUID.randomUUID().toString();
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, 30, TimeUnit.SECONDS);
if (locked) {
try {
// 执行业务:扣减库存
int remain = stockService.deductStock(productId, quantity);
if (remain < 0) throw new RuntimeException("库存不足");
} finally {
// 释放锁:Lua 保证只有自己能删
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockKey), requestId);
}
} else {
throw new RuntimeException("系统繁忙,请重试");
}
✅ 优点:
• 高性能(内存操作)
• 支持过期自动释放
• 广泛使用(Redisson 封装)
❌ 缺点:
• 主从切换可能导致锁失效(需用 RedLock)
• 时间漂移问题(锁过期但任务未完成)
方式二:基于 ZooKeeper 的分布式锁(高可靠)
原理:临时顺序节点 + Watch 机制
// Curator 框架示例
InterProcessMutex lock = new InterProcessMutex(client, "/locks/stock_" + productId);
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
// 扣减库存
stockMapper.decrease(productId, quantity);
}
} finally {
lock.release();
}
✅ 优点:
• 强一致性(ZAB 协议)
• 锁自动释放(会话断开即删除节点)
• 公平锁支持好
❌ 缺点:
• 性能低于 Redis
• 运维成本高(需部署 ZK 集群)
方式三:基于数据库的分布式锁(最简单,但不推荐高并发)
方式 A:唯一索引 + INSERT 抢占
CREATE TABLE distributed_lock (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
lock_key VARCHAR(64) NOT NULL UNIQUE,
owner_id VARCHAR(64),
expire_time DATETIME
);
抢占锁:
INSERT INTO distributed_lock (lock_key, owner_id, expire_time)
VALUES ('stock_123', 'user_456', NOW() + INTERVAL 30 SECOND);
-- 主键冲突则抢占失败
释放锁:DELETE FROM distributed_lock WHERE lock_key='stock_123' AND owner_id='user_456'
方式 B:UPDATE 乐观锁(更推荐)
UPDATE stock SET quantity = quantity - #{quantity}, version = version + 1
WHERE product_id = #{productId} AND quantity >= #{quantity};
配合版本号或 CAS 操作,天然防超卖。
✅ 优点:
• 无需额外中间件
• 简单易懂,适合中小项目
❌ 缺点:
• 性能差(DB 瓶颈)
• 锁持有时间长会阻塞连接池
• 不支持非阻塞获取
五、三种锁对比表(面试必备)
特性 Redis 锁 ZooKeeper 锁 数据库锁
一致性 最终一致 强一致 强一致
性能 ⭐⭐⭐⭐ ⭐⭐ ⭐
可靠性 中(需 RedLock) 高 低
实现复杂度 中 高 低
适用场景 秒杀、缓存击穿防护 金融、分布式协调 低频扣减、简单业务
推荐框架 Redisson Curator MyBatis + SQL
六、进阶:如何用 Lua + Redis 彻底避免超卖?(无锁胜有锁)
原子脚本示例(Redis 中执行):
-- KEYS[1]: stock:{productId}
-- ARGV[1]: 扣减数量
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock >= tonumber(ARGV[1]) then
redis.call('DECRBY', KEYS[1], ARGV[1])
return stock - tonumber(ARGV[1])
else
return -1 -- 库存不足
end
Java 调用:
DefaultRedisScript script = new DefaultRedisScript<>(luaScript, Long.class);
Long remain = redisTemplate.execute(script, Collections.singletonList(key), String.valueOf(quantity));
if (remain != null && remain >= 0) {
// 扣减成功,继续下单流程
}
👉 优势:无需分布式锁,单条命令完成判断+扣减,天然原子!
七、面试加分回答模板
面试官:说说电商库存扣减如何防超卖?
你可以这样回答:
“我们采用‘三级防护’机制:
- 前端限流 + 按钮置灰,减少无效请求;
Redis 预扣库存,通过 Lua 脚本保证原子性,避免并发超卖;
数据库最终扣减,使用乐观锁(version 或 CAS)确保数据一致;
为防止重复扣减,我们引入幂等性设计,通过订单号 + 状态机控制;
在高并发场景下,我们使用 Redisson 分布式锁 对关键资源加锁,确保同一时间只有一个请求能操作某商品库存。
此外,我们还设计了库存回补机制:用户超时未付款,通过定时任务将 Redis 和 DB 库存同步回补。”
八、避坑指南
坑 解决方案
锁忘记释放 用 try-finally 或看门狗机制(Redisson)
锁过期但任务没完 设置合理的过期时间 + 看门狗续期
主从切换丢锁 使用 RedLock 或 ZooKeeper
缓存与 DB 不一致 采用“Cache-Aside + 消息补偿”模式
重复扣减 订单号幂等校验 + 状态机(待支付→已支付)
九、总结
技术点 作用
分布式锁 控制并发,避免同时操作
原子操作(Lua) 替代锁,提升性能
乐观锁(CAS) 保证 DB 层不超卖
幂等性 防止重复扣减
最终一致性 通过 MQ 异步同步数据
十、推荐学习资源
• Redisson 官方文档:https://redisson.org/
• Curator 锁实现:https://curator.apache.org/
• 阿里《Java 开发手册》:关于锁的使用规范
• 极客时间《分布式技术原理与实战》
需要我为你画一个“库存防超卖全链路流程图”,用 Mermaid 或文字描述,方便你在面试中手绘展示吗?这样可以让回答更直观,提升印象分。