hyperf中redis分布式锁若未正确启用看门狗(如lock(30, seconds)),将大概率导致死锁;必须设leasetime为-1/null并全局配置lockwatchdogtimeout,且避免在@transactional内unlock。

Hyperf 里用 Redis 分布式锁,不配看门狗或配错参数,死锁风险极高——不是“可能”,是“大概率发生”。
为什么 lock(30, SECONDS) 会引发死锁
这个调用看似稳妥,实则关闭了自动续期能力:lock(30, TimeUnit.SECONDS) 中的 30 是正数,Redisson(及 Hyperf 封装层)直接跳过看门狗逻辑。锁在第 30 秒准时过期,不管业务是否还在执行。
常见后果:
- 订单扣库存耗时 35 秒 → 第 30 秒锁释放,第二个请求抢入,重复扣减
- 定时任务处理日志归档 → 锁提前失效,多个实例并发写同一文件,内容错乱
- 事务内加锁后 unlock() 提前触发 → 看门狗线程终止,后续续期停止,锁残留
正确启用看门狗的两个硬性条件
看门狗不是开关按钮,它由参数值和初始化配置共同决定:
本页面提供企业级 PHP 协程框架 Hyperf 3.1.66 版本的官方源码下载与完整更新日志。重点解析 v3.1.66 版本中新增的 gRPC 多客户端负载均衡支持、Pool 连接池全量刷新、Guzzle 持久化 Cookie 以及数据库 JSON 包含键查询等核心优化特性。
-
leaseTime必须为-1或null:调用lock()(无参)或tryLock(waitTime, -1, unit) -
lockWatchdogTimeout必须在Config初始化阶段全局设置:该值决定续期间隔(≈lockWatchdogTimeout / 3)和崩溃后锁最大残留时间 - 不能在
@Transactional方法内调用unlock():事务未提交前解锁,看门狗立即停摆,锁变成“半吊子”状态
Hyperf 中实际可落地的续期方案
Hyperf 自带的 RedisTaskMutex 已内置续期逻辑,但需确认是否启用;若自研锁,必须手写续期脚本或复用 malkusch/lock:
- 推荐组合:
composer require malkusch/lock+PHPRedisMutex,它默认使用SET key value NX PX并支持自动续期语义 - 手动续期示例(Lua 原子):
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("PEXPIRE", KEYS[1], ARGV[2]) else return 0 end - 避免用
EXPIRE:毫秒级精度下必须用PEXPIRE,否则续期可能失败 - 续期间隔建议设为业务平均耗时的 1/3~1/2:比如任务通常跑 90 秒,
lockWatchdogTimeout设为120000(2 分钟),续期间隔 ≈ 40 秒
最容易被忽略的三个失效场景
看门狗不是万能的,以下情况它完全静默失效,必须靠架构兜底:
-
Thread.sleep(60_000)后进程被kill -9→ 看门狗线程消失,锁只能等lockWatchdogTimeout后自动释放,无告警、无补偿 - Redis 集群脑裂,客户端连到只读从节点 → 续约 Lua 返回
0,后续所有操作抛RedisTimeoutException,但不会自动降级 - 运维误执行
DEL lock:order:123→ 看门狗仍在发续期请求,每次返回0,最终任务停止续期,锁已丢失却无感知
真正关键的不是“怎么续”,而是“续不上时系统是否还能稳住”——幂等设计、失败告警、人工干预通道,一个都不能少。










