应依赖注入lockinterface而非new redislock;set必须用['nx','ex'=>30]原子写入;解锁须用lua校验owner;库存扣减须在lua脚本内完成校验与变更。

直接用 LockInterface 注入,别 new RedisLock
Hyperf 的 RedisLock 类看着能直接实例化,但手动 new RedisLock($redis) 会绕过 DI 容器,导致 Redis 连接无法复用、锁生命周期失控——高并发下容易出现连接耗尽或锁自动释放失效。
正确做法是依赖注入 LockInterface,由 LockFactory 统一管理:
-
LockFactory默认已注册,无需额外配置 - 注入点写
protected LockInterface $lock;或构造函数/方法参数中声明 - 调用
$this->lock->get('redis', 'key')获取锁实例,底层自动选对连接池 - 必须配合
try/catch捕获LockTimeoutException,不能靠if (!lock())后续无处理
set 命令必须带 NX 和 EX 参数,单条原子写入
老式分两步:先 setnx 再 expire,中间若进程崩溃或网络中断,锁就永久残留。Hyperf 底层 Redis 客户端支持 Redis 2.6.12+ 的增强 set 语法,必须用一条命令完成。
实际生效的参数组合只有这一个安全写法:
-
['NX','EX'=>30]—— 不存在才设、30秒后自动过期 - 不能用
XX(只更新已存在 key),也不该漏掉EX或用PX却不统一单位 - Hyperf 默认
set调用已封装此逻辑,但如果你手写$redis->set(...),必须显式传入这两个选项
解锁必须用 Lua 脚本校验 owner,不能 del 硬删
直接 $redis->del('lock:key') 是危险操作:任何客户端都能删别人的锁。Hyperf 内置的 unlock 方法底层用了 Lua 脚本做原子校验,你如果自己实现,也得照搬。
关键点有三个:
-
owner必须是唯一值(如uniqid('lock_', true)),不能写死成字符串'1' - Lua 脚本必须同时完成「读 value」和「比对并删」,中间不能被插队
- 返回值要判断是否为
1,0表示不是你的锁,不该报错,而是静默忽略
库存扣减必须压进 Lua,PHP 层三步操作必超卖
查库存 → 判断是否 > 0 → decr,这三步在并发下完全不原子。哪怕加了分布式锁,只要锁粒度没包住整个库存校验+变更流程,或者锁提前释放,照样超卖。
正确姿势是把库存检查和扣减合并进一个 Lua 脚本执行:
- 脚本接收商品 ID 和扣减数量,
get当前库存,if stock >= need then decrby stock need; return 1 else return 0 end - PHP 层只关心返回值:1 成功,0 失败,不查不判不重试
- 别用
decr后再get判断结果,decr返回值可能为负数,得靠 Lua 里判断边界
最易被忽略的是:锁的持有时间必须覆盖 Lua 执行 + 业务处理全程,不能锁着查完库存就 unlock,否则后面 Lua 扣减时已经没锁保护了。











