题目
优化高并发下的商品库存扣减性能
信息
- 类型:问答
- 难度:⭐⭐
考点
数据库优化,缓存应用,锁机制选择
快速回答
优化高并发库存扣减的核心要点:
- 使用Redis缓存库存数据减少数据库压力
- 采用Lua脚本保证原子性操作
- 数据库层使用乐观锁防止超卖
- 引入队列削峰填谷处理并发请求
- 设置库存缓存预热和同步机制
问题场景
在电商秒杀场景中,当大量用户同时抢购某商品时,传统的直接操作数据库的方式会导致:
- 数据库连接池耗尽
- 行锁竞争导致死锁
- 响应时间急剧上升
- 库存超卖风险
优化方案
1. 缓存库存数据(Redis)
// 缓存预热(商品上线时)
$redis->set('stock:product_123', 1000);原理: 将库存数据加载到Redis内存数据库,减少直接访问MySQL的次数。
2. 原子扣减(Lua脚本)
$lua = <<<LUA
local key = KEYS[1]
local change = tonumber(ARGV[1])
local current = tonumber(redis.call('GET', key))
if current >= change then
return redis.call('DECRBY', key, change)
end
return -1
LUA;
$result = $redis->eval($lua, ['stock:product_123', 1], 1);作用: 保证在分布式环境下的原子操作,避免超卖。
3. 数据库最终更新(乐观锁)
// 异步处理数据库更新
$queue->push(function () use ($productId) {
DB::table('products')->where('id', $productId)
->where('version', $currentVersion) // 乐观锁
->decrement('stock', 1, ['version' => DB::raw('version + 1')]);
});原理: 通过version字段实现乐观锁,避免更新冲突。
4. 消息队列削峰
// 请求入队(Controller层)
RabbitMQ::publish('stock_queue', json_encode([
'user_id' => $userId,
'product_id' => $productId
]));作用: 将瞬时高并发请求转为顺序处理,保护后端系统。
最佳实践
- 分层校验: 前端限流 → 缓存校验 → 队列缓冲 → 数据库持久化
- 库存分段: 将总库存拆分为多个子库存段(如100件/段)分散压力
- 缓存预热: 活动开始前预加载数据到Redis
- 监控告警: 实时监控Redis内存/数据库负载
常见错误
- ❌ 在PHP代码中做库存计算(非原子操作)
- ❌ 使用SELECT FOR UPDATE导致数据库死锁
- ❌ 缓存与数据库双写不一致
- ❌ 未设置Redis操作重试机制
扩展知识
- Redis持久化: 结合AOF和RDB保证数据安全
- 限流策略: 使用令牌桶算法控制请求流量
- 熔断机制: 当Redis不可用时降级到数据库层
- 库存回滚: 订单取消时的库存恢复策略