锁续期与看门狗是什么
分布式锁必须设置过期时间,否则持锁进程崩溃会永久死锁。但业务执行时间可能超过这个长度,于是需要后台看门狗定期延长过期时间——前提是锁仍属于自己,否则不能续。续期本身也有边界:进程长时间停顿时续期会一起停,锁过期后就可能出现两个持有者。
基本信息
| 续期条件 | 锁的值仍等于自己写入的令牌才允许延长 |
|---|---|
| 续期间隔 | 常见做法是锁时长度的三分之一 |
| 实现方式 | 用脚本把比对与延长合并为一个原子操作 |
| 失败处理 | 续期失败或网络异常时应尽快中止业务并核对数据 |
| 代价 | 额外心跳流量,锁数量大时会放大实例压力 |
典型使用场景
- 长耗时任务:导出、对账等执行时间不可预估,需要动态续期
- 定时任务防重:同一任务在多实例部署时只允许一个执行
- 库存与订单串行化:把并发操作收敛到单实例处理
- 跨服务资源互斥:多个服务争用同一外部资源时做互斥
- 与关闭钩子配合:任务结束时主动释放锁,而不是等它过期
续期脚本
续期脚本
-- 仍持有才刷新过期时间(毫秒),返回 1 表示续期成功
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('PEXPIRE', KEYS[1], ARGV[2])
else
return 0
end调用与释放
调用与释放
# 加锁:令牌唯一,过期时间 30 秒
SET lock:order:42 6f1d2c4e NX PX 30000
# 续期:每 10 秒执行一次(锁时长度的三分之一)
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('PEXPIRE', KEYS[1], ARGV[2]) else return 0 end" 1 lock:order:42 6f1d2c4e 30000
# 释放:比对令牌后删除,避免删掉别人的锁
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" 1 lock:order:42 6f1d2c4e参数与节奏
| 要素 | 取值建议 | 说明 |
|---|---|---|
| 锁时长 | 预估最大执行时间加余量 | 过短会频繁续期,过长会延长故障影响 |
| 续期间隔 | 锁时长的三分之一 | 留出容错窗口,避免临界过期 |
| 令牌 | 随机值且唯一 | 用于续期与释放时的归属校验 |
| 等待获取 | 设定超时并快速失败 | 避免请求堆积在抢锁上 |
续期返回零说明锁已经不是自己的:此时应立即中止业务,而不是继续执行下去。
进程长时间停顿时续期线程同样停止:锁过期后出现双持有者窗口,关键业务要在数据层加唯一约束等兜底。