根本原因是lock(30, SECONDS)显式指定正数leaseTime,导致Redisson及Hyperf跳过看门狗初始化;必须用lock()或tryLock(waitTime, -1, unit)并全局配置lockWatchdogTimeout,且避免在@Transactional内unlock。

Hyperf 中 Redis 分布式锁提前释放,根本原因不是 Redis 不够快,而是看门狗没启动或被意外关掉——lock(30, TimeUnit.SECONDS) 这种写法直接禁用续期,锁必在 30 秒准时“下班”,业务还没跑完就已失效。
为什么 lock(30, SECONDS) 会让看门狗彻底失能
Redisson(及 Hyperf 封装层)的判断逻辑非常直接:只要 leaseTime 是正数,就跳过看门狗初始化。这不是配置遗漏,是源码级硬约束。
-
lock()(无参)→ leaseTime = -1 → 看门狗自动激活,按lockWatchdogTimeout续期 -
lock(30, SECONDS)→ leaseTime = 30 → 看门狗逻辑被绕过,锁纯靠 Redis 自身过期 - Hyperf 的
RedisTaskMutex若未显式启用续期,也默认走固定过期路径,不自动 fallback 到看门狗
Hyperf 里真正启用看门狗的三步落地操作
Hyperf 没有“一键开启看门狗”的开关,必须同时满足三个条件,缺一不可:
- 加锁时调用
lock()或tryLock(waitTime, -1, unit),确保leaseTime为-1或null - 在
config/autoload/redis.php或容器初始化阶段,全局设置:config(['redis.options.lock_watchdog_timeout' => 120000])(单位毫秒,建议 ≥ 业务 P99 耗时 × 2) - 避免在
@Transactional方法内调用unlock():事务未提交前解锁,会强制终止看门狗线程,导致后续续期全部失效
手动续期 Lua 脚本必须用 PEXPIRE,不能用 EXPIRE
Hyperf 默认使用 PHPRedis,而 EXPIRE 只接受秒级整数,PEXPIRE 才支持毫秒精度。看门狗续期若用错命令,会在毫秒级偏差下失败(比如设了 120000ms,但 EXPIRE 强制截断为 120s,实际只剩 119.999s)。
- 安全续期 Lua 示例:
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("PEXPIRE", KEYS[1], ARGV[2]) else return 0 end -
ARGV[2]必须传毫秒值(如120000),不能传秒(如120) - PHPRedis 调用时需确保连接启用了
setOption(\Redis::OPT_PREFIX, ''),否则 key 前缀可能干扰 Lua 中的KEYS[1]匹配
最容易被忽略的三个静默失效点
看门狗不是永动机,以下情况它完全不报警、不补偿、只默默停摆:
-
Thread.sleep(60_000)后进程被kill -9→ JVM 直接销毁,看门狗线程消失,锁只能等lockWatchdogTimeout后自动释放 - Redis 集群脑裂,Hyperf 客户端连到只读从节点 →
PEXPIRE返回 0,续期失败但无异常抛出,锁实际已丢失 - 运维误执行
DEL lock:order:123→ 看门狗仍持续发续期请求,每次返回 0,最终任务停止续期,但业务线程毫无感知
这些场景无法靠参数调整规避,必须配合 Redis 监控(key 存活时间趋势)、客户端心跳上报、以及关键路径的锁持有超时告警来兜底。


















