直接用 session.create + kv.put 会丢锁,因为 consul session 默认 ttl=10s 且不自动续期,过期后 key 被删导致锁失效;必须通过 goroutine 定期调用 session.renew 续期,并绑定 context 控制生命周期,避免幽灵续期。

为什么直接用 session.Create + kv.Put 会丢锁
Consul 的分布式锁本质是“带 session 的 KV 写入”,但很多人只调一次 kv.Put 就以为锁住了,结果几秒后锁自动失效。根本原因是:Consul 的 session 默认 TTL=10s,且不会自动续期;一旦 session 过期,关联的 key 立即被删除,锁就丢了。
真正可靠的锁必须让 session 在持有期间持续心跳。Go 官方 consul-api 不提供自动续期封装,得自己拉起 goroutine 定期调 session.Renew。
-
session.Renew必须传入原始 session ID(不是 name),且返回新 TTL 剩余时间,不能只看 err 是否为 nil - 续期间隔建议设为
TTL/3(比如 TTL=30s,每 10s 续一次),太频繁增加 consul 压力,太慢有窗口期风险 - 如果 renew 返回
404(Unexpected response code: 404),说明 session 已被销毁,当前锁已失效,应立即退出并释放本地状态
如何安全地启动和管理续期 goroutine
续期逻辑不能裸跑 goroutine,否则锁释放时无法停止它,可能造成“幽灵续期”——锁早被别人抢走,你还傻乎乎续着旧 session,污染 consul 状态。
正确做法是把续期和锁生命周期绑定:用 context.WithCancel 控制 goroutine 生命周期,锁释放时 cancel context,续期 goroutine 自然退出。
- 创建 session 时务必设置
Behavior: "delete"(默认值),确保锁释放后 key 被清理;不要用"release",它依赖客户端主动 release,不可靠 - 续期 goroutine 内部要检查 context 是否 Done,每次 renew 后 sleep 前都判断一次,避免 cancel 后还 sleep
- 在 renew 失败(非 404)时,比如网络超时,应重试 1–2 次再放弃;但遇到 404 必须立刻 return,不能再 retry
锁获取失败时怎么避免忙等
Consul 锁不支持阻塞等待,kv.Put 失败就直接返回 false。如果业务需要“等锁”,不能写 for 循环空转,得用 watch + blocking query 机制。
核心思路:用 kv.Get 的 WaitIndex 参数监听锁 key 的变更,当 key 被删(别人释放)或 value 改变(别人续期失败),立刻重试获取。
- 首次
kv.Get加WaitTime=5s和WaitIndex,避免长连接卡死;超时后重新查当前状态,再决定是否继续 wait - watch 到变更后,必须重新调
session.Create+kv.Put,不能复用旧 session —— session 是一次性凭证,不能跨锁使用 - 每次 wait 前检查 context 是否 Done,防止 goroutine 泄漏
实际代码里最容易漏掉的三个点
很多实现跑测试没问题,一上生产就丢锁,问题往往出在边界处理上:
- 没校验
session.Create返回的ID是否为空字符串 —— 创建失败时 ID 是空,后续所有Renew都 404,但代码没判空就直接用了 - 续期 goroutine panic 了没 recover,导致整个服务 goroutine 泄漏;至少包一层
defer func(){recover()}() - 锁 key 的 value 写成了固定字符串(如
"locked"),其实应该写入本机唯一标识(如 hostname+pid),方便 debug 时查是谁持锁
自动续期不是加个 goroutine 就完事,它把单次操作变成了带状态、有时序依赖的长周期过程。最危险的不是拿不到锁,而是“以为自己还拿着”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











