PHP+Redis分布式锁需原子加锁(SET key value NX EX)、唯一value校验解锁、Lua脚本安全释放,并配合库存DB校验与原子更新防超卖。

PHP + Redis 实现分布式锁,核心是用原子操作保证互斥,同时靠唯一标识和自动过期规避死锁,再用 Lua 脚本确保安全释放——超卖和误删锁的问题就卡在这三个环节上。
加锁必须原子:SET key value NX EX 秒数
不能分两步做 SETNX + EXPIRE,否则中间可能被其他请求抢占。Redis 的 SET 命令支持 NX(不存在才设)和 EX(秒级过期)组合,一条命令完成加锁+过期设置,彻底避免竞态。
- key 建议带业务前缀,比如 stock:1001 表示商品 ID 为 1001 的库存锁
- value 必须是当前客户端唯一标识,推荐用 uniqid('', true) 或 UUIDv4,后续解锁时用来校验归属
- EX 时间不是越长越好,一般设为业务逻辑最大耗时的 2–3 倍(如扣库存+下单共 3 秒,可设 10 秒),太短易误释放,太长影响吞吐
解锁必须校验:Lua 脚本保证“是我设的才删”
直接 DEL key 是危险的——A 拿到锁,执行慢超时了;B 在 A 过期后抢到锁;A 接着执行 del,就把 B 的锁给删了。所以解锁必须先 get 判断 value 是否匹配,再 del,且这两步必须原子执行。
- 用 Redis::eval() 执行这段 Lua:if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
- 调用时传入 lockKey 和当初 set 的那个唯一 value,返回 1 表示删除成功,0 表示锁已不属于你
- 务必在 try...finally 中调用 release(),哪怕业务抛异常也要确保释放
防死锁与续期:超时不是万能,关键操作长时需自动续期
固定过期时间只能兜底,无法应对实际耗时远超预期的情况(比如数据库慢查询、第三方接口卡顿)。生产环境建议引入看门狗机制,在锁持有期间定期刷新 TTL。
立即学习“PHP免费学习笔记(深入)”;
- 可用单独协程或后台定时任务,每 5 秒检查一次锁是否还在自己手上,若在则执行 EXPIRE key new_ttl
- 更稳妥的做法是使用 think-redis-lock 这类封装好的扩展,它内置心跳续期和可重入支持
- 如果不用扩展,手写续期逻辑时注意:续期前先 get 校验 value,防止续了别人的锁
超卖场景落地要点:锁粒度、库存校验、事务配合
锁只是手段,不是终点。光加锁不等于防住超卖,还得配合正确的库存判断和数据更新方式。
- 锁的 key 粒度要够细,推荐按商品 ID 锁,而不是全局限流锁;高并发下可进一步按分片(如 stock:1001:shard0)降低热点
- 获取锁后,**仍需查一次数据库最新库存**(SELECT stock FROM goods WHERE id = ? FOR UPDATE),再扣减,避免缓存延迟导致误判
- 扣减库存建议用 SQL 原子操作:UPDATE goods SET stock = stock - 1 WHERE id = ? AND stock > 0,用影响行数判断是否成功,失败则立即释放锁并返回“库存不足”



















