结论错误:lock.lock(30, TimeUnit.SECONDS)不会自动续期;只有lock()或lock(-1, TimeUnit.SECONDS)才启用WatchDog自动续期。

直接说结论:用 Redisson 的 lock.lock(30, TimeUnit.SECONDS) 就自带自动续期,不用手写;如果坚持手撸,必须用 Lua 脚本 + 定时任务轮询续期,否则锁会在业务中途过期,导致并发安全问题。
为什么简单 setex + del 不能续期
很多人以为“加个定时器去 expire 延长”就行,但这是错的。根本问题是:EXPIRE 不是原子操作,且无法判断当前 key 是否仍属于本客户端。比如线程 A 续期时,锁已被 B 获取,A 还在给 B 的锁续命,不仅无效,还可能干扰正常锁逻辑。
常见错误现象:
- 业务执行到一半,锁过期,其他实例抢入,数据被双写
- 续期线程误续了别人持有的锁,导致真正持有者 unlock 失败
- 续期任务没做幂等或超时判断,CPU/Redis 负载飙升
Redisson 自带续期怎么用
Redisson 的 RLock 在加锁成功后,会自动启动一个「看门狗」线程(默认每 10 秒续一次),续期时间 = lockWatchdogTimeout(默认 30 秒),只要锁还被当前线程持有,就持续续期直到 unlock 或线程死亡。
实操建议:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 加锁必须用
tryLock(long waitTime, long leaseTime, TimeUnit unit)或lock(long leaseTime, TimeUnit unit),其中leaseTime > 0才触发看门狗;传-1或不设,则用默认 30 秒 - 不要手动调
expire或refreshExpire,会破坏看门狗机制 - 确保
redissonClient是单例,否则看门狗线程可能重复启动 - 若业务耗时极长(如 > 30 分钟),需显式调大
lockWatchdogTimeout配置,否则看门狗会停掉
示例:
RLock lock = redissonClient.getLock("order:pay:20260713001");<br>lock.lock(60, TimeUnit.SECONDS); // 拿锁并启用看门狗,每 10s 续一次,续到 60s 后自动停
手写续期必须满足的三个条件
不用 Redisson 时,手写续期不是“加个定时器”那么简单,必须同时满足:
- 续期动作本身用 Lua 脚本执行,校验 key 对应 value 是否等于本客户端唯一 ID(如 UUID)
- 续期任务需绑定到当前锁实例生命周期,不能全局共享一个线程池
- 必须有失效兜底:比如检测到锁已丢失(
get返回 null 或非本 client ID),立即终止续期并报错
关键 Lua 示例(用于续期):if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("pexpire", KEYS[1], ARGV[2]) else return 0 end
注意:ARGV[2] 应为毫秒级新过期时间(不是累加值),每次续期都是重置 TTL。
续期失败的真实信号是什么
续期失败 ≠ 立即出问题,但它是危险信号。常见原因和应对:
-
EVAL返回 0:说明锁已被别人覆盖,当前线程已失去所有权,应立刻中断业务、记录告警,而不是继续执行 - Redis 连接超时:说明服务不稳定,此时续期失败比锁过期更严重,建议熔断本次请求
- 看门狗线程被 GC STW 卡住超过 10 秒:JVM 层面问题,需查 GC 日志,不是锁实现能解决的
最容易被忽略的一点:续期逻辑只解决「锁不过期」,但不解决「业务异常未 unlock」。所以无论是否续期,finally 块里的 unlock() 仍必不可少,且要判空 + 判持有者。

















