×

《淘宝双11同款:基于 Sentinel 的微服务流量防卫兵实战》

万邦科技Lex 万邦科技Lex 发表于2026-03-23 13:38:50 浏览19 评论0

抢沙发发表评论

这个问题是电商面试中的高频题,涉及业务一致性分布式系统核心技术。下面我将为你撰写一篇结构清晰、技术深入的文章,既适合学习,也适合面试准备。

🛒 《面试官:说说电商库存扣减如何防超卖?分布式锁的三种实现》

“秒杀不到,库存没了;秒杀到了,库存却超了。” —— 这是电商最怕的事故之一。
在电商系统中,库存扣减是核心业务,一旦出错,轻则用户投诉,重则资损赔偿。而高并发场景下,如何防止“超卖”(Over-Selling)?答案离不开:分布式锁 + 原子操作 + 幂等性设计
本文将深入讲解:
  1. 超卖是如何发生的?

  2. 如何设计安全的库存扣减流程?

  3. 三种主流分布式锁实现方式对比

  4. 面试加分项:Redisson、ZooKeeper、数据库锁


一、什么是“超卖”?

超卖:实际卖出的商品数量 > 库存数量。

示例场景:

  • 商品库存:10 件

  • 同时 15 人下单成功 → 超卖 5 件

  • 结果:无法发货,引发客诉、退款、平台信誉受损


二、超卖的根本原因

原因
说明
并发读写未加锁
多个线程同时读取库存为 10,各自扣减,最终扣成负数
非原子操作
查询库存 + 判断 + 扣减 三步分离,中间被插队
缓存与DB不一致
Redis 预扣库存,但 DB 更新失败,未回滚
重试机制不当
消息重试导致重复扣减

三、正确库存扣减流程设计

✅ 核心原则:原子性 + 幂等性 + 最终一致性

推荐流程(以秒杀为例):

1. 用户点击购买 → 进入排队队列
2. 检查库存(Redis 预扣)
3. 扣减 Redis 库存(Lua 脚本保证原子性)
4. 发送 MQ 消息 → 异步扣减数据库
5. 返回“抢购中”状态
6. 消费者消费消息 → 扣减 DB 库存
7. 扣减成功 → 通知用户付款
8. 超时未付款 → 回补库存(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<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
Long remain = redisTemplate.execute(script, Collections.singletonList(key), String.valueOf(quantity));
if (remain != null && remain >= 0) {
    // 扣减成功,继续下单流程
}
👉 优势:无需分布式锁,单条命令完成判断+扣减,天然原子!

七、面试加分回答模板

面试官:说说电商库存扣减如何防超卖?
你可以这样回答
“我们采用‘三级防护’机制:
  1. 前端限流 + 按钮置灰,减少无效请求;

  2. Redis 预扣库存,通过 Lua 脚本保证原子性,避免并发超卖;

  3. 数据库最终扣减,使用乐观锁(version 或 CAS)确保数据一致;

  4. 为防止重复扣减,我们引入幂等性设计,通过订单号 + 状态机控制;

  5. 在高并发场景下,我们使用 Redisson 分布式锁 对关键资源加锁,确保同一时间只有一个请求能操作某商品库存。

此外,我们还设计了库存回补机制:用户超时未付款,通过定时任务将 Redis 和 DB 库存同步回补。”

八、避坑指南

解决方案
锁忘记释放
try-finally或看门狗机制(Redisson)
锁过期但任务没完
设置合理的过期时间 + 看门狗续期
主从切换丢锁
使用 RedLock 或 ZooKeeper
缓存与 DB 不一致
采用“Cache-Aside + 消息补偿”模式
重复扣减
订单号幂等校验 + 状态机(待支付→已支付)

九、总结

技术点
作用
分布式锁
控制并发,避免同时操作
原子操作(Lua)
替代锁,提升性能
乐观锁(CAS)
保证 DB 层不超卖
幂等性
防止重复扣减
最终一致性
通过 MQ 异步同步数据

十、推荐学习资源


需要我为你画一个“库存防超卖全链路流程图”,用 Mermaid 或文字描述,方便你在面试中手绘展示吗?这样可以让回答更直观,提升印象分。


群贤毕至

访客