跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

高并发秒杀:PHP+Redis+Lua脚本实现库存扣减方案

Redis+Lua可原子扣减库存防超卖,方案包括:Lua脚本直接DECRBY、Hash结构校验后HINCRBY、分布式锁+PHP事务、预减队列+异步落库。 如果在高并发秒杀场景中直接使用PHP操作MySQL扣减库存,容易出现超卖问题。以下是基于PHP、Redis与Lua脚本协同完成原子化库存扣减的多种实现方案: 一、Lua脚本嵌入Redis执行原子扣减 利用Redis单线程特性及Lua脚本在服务端原子执行的能力,将库存校验与扣减封装为一个不可分割的操作,彻底规避竞态条件。 1、在Redis中预先设置商品库存键,例如: SET sku:1001:stock 100 。 2、编写Lua脚本内容并保存为
decr_stock.lua
: 立即学习 “ PHP免费学习笔记(深入) ”; ifredis.call("GET", KEYS[1]) >= tonumber(ARGV[1]) then\n return redis.call("DECRBY", KEYS[1], ARGV[1])\nelse\n return -1\nend 3、PHP中调用该脚本: $redis->eval(file_get_contents('decr_stock.lua'), ['sku:1001:stock'], [1]); 二、Redis Hash结构+HINCRBY结合条件判断 使用Hash结构存储多维度库存信息(如总库存、已售数量),通过Lua脚本读取并判断剩余可用库存,再执行原子更新。 1、初始化Hash数据: HSET sku:1001 stock 100 sold 0 。 2、Lua脚本逻辑如下: local stock = tonumber(redis.call("HGET", KEYS[1], "stock"))\nlocal sold = tonumber(redis.call("HGET", KEYS[1], "sold"))\nif (stock - sold) >= tonumber(ARGV[1]) then\n return redis.call("HINCRBY", KEYS[1], "sold", ARGV[1])\nelse\n return -1\nend 3、PHP中执行: $redis->eval($lua_script, ['sku:1001'], [1]); 三、Redis分布式锁 + PHP事务性库存操作 在不依赖Lua的前提下,通过SETNX获取分布式锁,确保同一商品在同一时刻仅有一个请求进入库存处理流程,后续由PHP控制校验与更新。 1、生成唯一锁键: $lockKey = 'lock:sku:1001'; 2、尝试加锁并设置过期时间: PHP 8.5.5 PHP 8.5.5 是 PHP 8.5 分支的维护更新版本。该版本延续了“小步快跑”的迭代逻辑,通过深度错误修复、底层性能微调以及安全加固,旨在为开发者提供一个更健壮、更高效的运行环境。该版本严格遵守语义化版本规范,不包含破坏性变更。 下载 if ($redis->set($lockKey, 1, ['nx', 'ex' => 5])) { ... } 3、加锁成功后执行以下步骤: 1、从Redis读取当前库存值; 2、判断是否满足扣减条件; 3、满足则执行
DECRBY
或更新Hash字段; 4、无论成功与否,均需释放锁: $redis->del($lockKey); 四、预减库存队列 + 异步落库补偿 将秒杀请求快速写入Redis List作为预减队列,由后台消费者进程逐条处理并同步MySQL,前端仅依据Redis结果响应,降低数据库瞬时压力。 1、用户请求到达时,向队列推入订单信息: LPUSH seckill:queue '{"sku_id":1001,"user_id":123}' 。 2、独立Worker进程阻塞弹出队列项: BRPOP seckill:queue 0 。 3、Worker执行以下动作: 1、查询Redis中对应SKU剩余库存; 2、若大于0,则执行
DECR
并记录预占记录; 3、发起MySQL插入订单,并更新真实库存; 4、根据MySQL事务结果回调Redis确认或回滚预占。

相关文章