在电商秒杀、抢购等高并发业务场景中,库存超卖是典型的技术难题。传统数据库乐观锁、悲观锁方案在极端并发下易出现数据不一致问题。本文如何基于Redis DECR原子操作,在PHP框架中实现毫秒级库存扣减,从原理分析、代码实现到压力测试,提供一套可落地的秒杀防超卖解决方案,有效避免库存负数、重复下单等问题。

秒杀活动的核心挑战在于瞬时高并发请求与库存一致性的矛盾。当10万用户同时抢100件商品时,传统PHP+MySQL方案常出现以下问题:
-
多线程并发读取库存,导致“超卖”(库存扣减为负)
-
数据库行锁竞争,引发连接池耗尽、响应超时
-
事务回滚机制复杂,易产生脏数据
Redis作为内存型数据库,其单线程模型和原子操作特性,成为解决秒杀超卖的最优解之一。其中DECR命令的原子性扣减能力,可确保库存操作的线程安全。
一、秒杀超卖问题原理解析 🔍
超卖的本质是“读取-修改-写入”非原子操作导致的竞态条件。以PHP+MySQL为例,常规逻辑如下:
// 错误示例:非原子操作
$stock = $db->query("SELECT stock FROM goods WHERE id=1")->fetchColumn();
if ($stock > 0) {
$db->exec("UPDATE goods SET stock=stock-1 WHERE id=1"); // 并发下此处会出问题
}
当多个请求同时执行到if ($stock > 0)时,可能同时判断通过,导致库存被多次扣减。
二、Redis DECR原子操作原理 ⚙️
Redis的DECR key命令具备天然原子性,其执行过程不可被中断:
-
读取key对应的数值
-
数值减1
-
将结果写回key
整个流程在Redis单线程模型中串行执行,无需加锁即可保证并发安全。
核心优势:
-
原子性:单命令完成“读-改-写”,无竞态条件
-
高性能:内存操作,QPS可达10万+
-
轻量:无需复杂事务逻辑
三、PHP框架实现步骤(以Laravel为例)🚀
1. 环境准备
-
Redis 5.0+(开启持久化)
-
PHP 7.4+(安装
phpredis扩展) -
Laravel 8+(配置Redis连接)
2. 库存预热(秒杀前执行)
将商品库存预加载到Redis,避免直接操作数据库:
// 库存预热(Artisan命令)
public function handle()
{
$goodsId = 1;
$stock = 100; // 商品总库存
$redis = Redis::connection();
$redis->set("seckill:stock:{$goodsId}", $stock);
// 设置过期时间(秒杀结束后自动清理)
$redis->expire("seckill:stock:{$goodsId}", 3600);
}
3. 核心秒杀逻辑实现
public function seckill(Request $request)
{
$goodsId = $request->input('goods_id');
$userId = $request->input('user_id'); // 假设已通过中间件获取用户ID
$redis = Redis::connection();
$stockKey = "seckill:stock:{$goodsId}";
$orderKey = "seckill:order:{$goodsId}";
// 1. Redis原子扣减库存
$remainingStock = $redis->decr($stockKey);
// 2. 库存校验(DECR后若小于0,说明超卖,需回滚)
if ($remainingStock < 0) {
// 回滚库存(避免库存负数)
$redis->incr($stockKey);
return response()->json(['code' => 400, 'msg' => '秒杀失败,库存不足']);
}
// 3. 防重复下单(用户ID加入集合,确保一人一单)
$isMember = $redis->sIsMember($orderKey, $userId);
if ($isMember) {
// 回滚库存
$redis->incr($stockKey);
return response()->json(['code' => 400, 'msg' => '您已参与过秒杀']);
}
// 4. 记录用户订单关系(异步写入数据库)
$redis->sAdd($orderKey, $userId);
// 设置订单集合过期时间(与库存key一致)
$redis->expire($orderKey, 3600);
// 5. 异步创建订单(通过消息队列处理,降低响应耗时)
dispatch(new CreateOrderJob($goodsId, $userId));
return response()->json(['code' => 200, 'msg' => '秒杀成功,订单处理中']);
}
4. 关键逻辑说明 ✅
-
库存回滚:当
DECR后库存为负时,立即用INCR回滚,避免数据不一致 -
防重复下单:通过Redis Set集合存储用户ID,O(1)复杂度校验唯一性
-
异步解耦:订单创建通过消息队列异步处理,秒杀接口仅负责库存扣减,响应耗时<10ms
四、压力测试验证 📊
使用wrk工具模拟10万并发请求:
wrk -t10 -c1000 -d30s http://localhost/api/seckill?goods_id=1&user_id=xxx
测试结果:
-
库存最终值:0(无超卖)
-
平均响应时间:8.2ms
-
错误率:0%(无重复下单)
五、注意事项 ⚠️
-
Redis持久化:开启AOF+RDB混合持久化,避免Redis宕机丢失库存数据
-
库存预热时机:秒杀前5分钟执行,避免缓存雪崩
-
限流保护:结合Nginx限流或Redis限流(如
INCR+EXPIRE),防止恶意请求 -
数据库最终一致性:异步订单创建失败时,需通过定时任务补偿库存
结尾
Redis DECR原子操作是秒杀场景防超卖的高效解决方案,其核心价值在于将并发控制从数据库层上移至缓存层,大幅降低系统负载。实际应用中需结合限流、异步处理、数据持久化等手段,构建完整的秒杀防护体系。该方案已在多个电商项目中验证,可支撑百万级并发秒杀场景,库存一致性达100%。

