今天给大家带来一道美团二面的高频场景题——“如何解决高并发场景下的超卖问题”。这道题看似基础,但90%的候选人都栽在细节实现上!本文将带你从问题分析到代码实现,手把手教你如何用分布式锁解决超卖问题。文末有完整代码实现,建议收藏!

🛑 问题现场还原
假设某电商平台进行iPhone15秒杀活动,库存100件,瞬时并发请求达到5W+/秒。技术部接到用户投诉:实际售出112件商品,出现严重超卖!请分析原因并给出解决方案。
为什么会超卖?
超卖的本质是共享资源(库存)的并发写问题。在高并发场景下,多个线程同时操作库存,导致数据不一致。核心问题在于:
-
非原子操作:查询库存 → 判断充足 → 扣减库存,这三个步骤不是原子操作。
-
数据库更新丢失:多个线程同时读到相同的库存值,导致更新丢失。
💡 初级工程师的翻车答案
很多初级工程师会回答:“用synchronized锁住减库存方法就行。”
美团面试官眉头一皱:“集群部署怎么办?锁失效怎么办?锁性能怎么保证?”
显然,这种答案只适用于单机环境,无法解决分布式场景下的并发问题。
🚀 正确解题思路拆解
1️⃣ 问题根源分析
超卖问题的核心在于共享资源的并发写。我们需要一种机制来保证同一时间只有一个线程可以操作库存。在分布式系统中,单机的synchronized或ReentrantLock无法满足需求,因此需要引入分布式锁。
2️⃣ 分布式锁解决方案演进史
-
synchronized:单机锁,无法跨JVM。
-
数据库行锁:性能差,不适合高并发场景。
-
Redis单机锁:基于
setnx命令,但需要手动处理锁超时和续期。 -
Redisson分布式锁:基于Redis,支持自动续期、可重入、公平锁等特性,是生产环境的首选。
3️⃣ 美团生产级方案(Redisson实现)
美团在生产环境中使用Redisson作为分布式锁的实现方案。Redisson提供了丰富的分布式锁特性,包括自动续期、可重入、公平锁等,能够很好地解决高并发场景下的超卖问题。
🔒 核心代码实现
分布式锁服务
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class InventoryService {
private final RedissonClient redisson;
private static final String LOCK_KEY = "stock_lock_%s"; // 商品维度锁
@Autowired
private StockMapper stockMapper;
public InventoryService(RedissonClient redisson) {
this.redisson = redisson;
}
public boolean deductStock(Long itemId, int num) {
// 获取分布式锁
RLock lock = redisson.getLock(String.format(LOCK_KEY, itemId));
try {
// 尝试加锁,最多等待100ms,锁自动释放时间30s
if (lock.tryLock(100, 30000, TimeUnit.MILLISECONDS)) {
// 查询库存
Stock stock = stockMapper.selectById(itemId);
if (stock.getQuantity() >= num) {
// 扣减库存
stock.setQuantity(stock.getQuantity() - num);
return stockMapper.updateById(stock) > 0;
}
return false; // 库存不足
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放锁
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return false; // 获取锁失败
}
}
库存实体类
public class Stock {
private Long id;
private String itemName;
private Integer quantity;
// Getters and Setters
}
库存Mapper接口
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface StockMapper {
Stock selectById(@Param("id") Long id);
int updateById(@Param("stock") Stock stock);
}
4️⃣ 关键实现细节
✅ 锁粒度控制
按商品ID维度加锁,提升并发性能。例如,stock_lock_123表示对商品ID为123的库存加锁。
✅ 锁等待时间
设置合理的tryLock等待时间(如100ms),避免线程堆积。如果获取锁失败,可以快速返回,避免长时间阻塞。
✅ 自动续期机制
Redisson默认30秒锁过期,并通过看门狗机制自动续期,防止锁提前释放。
✅ 锁释放判断
释放锁时必须验证当前线程是否持有锁,避免误释放其他线程的锁。
✅ 异常处理
确保在finally块中释放锁,防止锁无法释放导致死锁。
5️⃣ 压力测试对比
使用JMeter模拟5W并发测试:
-
无锁方案:超卖率12.3%
-
Redisson方案:零超卖,吞吐量保持1.2W TPS
💣 常见踩坑点
-
误用
setnx命令未设置超时时间:导致死锁。 -
未实现可重入锁:同一线程重复加锁失败。
-
未处理网络抖动:锁提前释放导致并发问题。
🛠️ 生产环境优化方案
-
锁等待队列:配置公平锁,避免线程饥饿。
-
库存预扣减:使用Redis缓存库存,减少数据库压力。
-
熔断降级策略:当锁竞争超过阈值时,触发熔断降级,保护系统。
📦 完整代码获取
看到这里,相信你已经get到大厂面试官的真实考察点。不过纸上得来终觉浅,我准备了完整的测试用例代码,包含20个并发测试场景模拟。需要的同学在评论区留言**“秒杀方案”**,我私发给你~
💬 互动讨论
大家在面试中还遇到过哪些头疼的并发场景题?欢迎在评论区讨论。关注我不迷路!
975

被折叠的 条评论
为什么被折叠?



