直接用concurrency.election可跑通选主,但90%线上故障切换失败源于续租失败后未及时退场:lease需显式keepalive,过期后key被删而程序仍运行;必须监听observe通道并处理关闭,否则无法感知被踢出。

直接用 concurrency.Election 就能跑通基本选主,但线上真正扛住故障切换的,90% 都栽在续租失败后没及时退场。
为什么不能只靠 election.Proclaim() 成功就认为稳了
成功调一次 Proclaim() 只代表你抢到了初始 leader key,不代表你能一直 hold 住。etcd 的 lease 会过期,而 Go 客户端不会自动帮你重续——它只在你显式调用 Lease.KeepAlive() 时才续。一旦网络抖动、etcd 压力大或 goroutine 被阻塞,续租请求失败,lease 就 expire,key 被自动删掉,但你的程序可能还在 happily 处理任务。
-
context.DeadlineExceeded或lease expired错误出现后,Election.IsLeader()仍可能返回true(因为本地没刷新) - 不检查
Observe()通道,就等于放弃 etcd 主动推送的“你已被踢出”信号 - 多个实例共用同一个
concurrency.Election对象,CAS 冲突率飙升,leader 切换像抽风
concurrency.Election.Observe() 必须监听,且要处理 channel 关闭
Observe() 返回一个 chan *concurrency.ElectionResponse,它会在 leader 身份变更(被抢占、lease 过期、key 被删)时推一条消息。但它不是永不停止的 stream:一旦底层 lease 彻底失效或连接断开,这个 channel 会被 close。
- 必须用
select+case + <code>case 双重判断,不能只读不判空 - 收到
nil响应或 channel 关闭,立刻退出 leader 工作循环(比如停止消费任务队列、关闭定时器) - 别在
Observe()的 goroutine 里做耗时操作,否则会卡住后续通知
写 leader key 时必须带 LeaseID 并校验 version == 0
不用 concurrency.Election 手撸的话,核心就是两个原子操作组合:先用 Lease.Grant() 拿租约,再用 KV.Put() 写 key,且必须带上 LeaseID 和 cmp.Version("key") == 0 条件。
- 漏传
LeaseID→ key 不绑定租约 → lease 到期 key 不删 → 其他节点无法上位,形成“假 leader” - 不用
cmp.Version("key") == 0→ 后来者 Put 直接覆盖前人,多个节点同时认为自己是 leader,脑裂 - 绝对不要用
KV.Put()不带 CompareAndSwap 条件 —— 它等价于裸写,完全破坏选主语义
续租 goroutine 要独立运行,并监控 Lease.KeepAlive() 返回流
Lease.KeepAlive() 返回一个 chan *clientv3.LeaseKeepAliveResponse,它每秒发一次心跳响应。但这个 channel 一旦底层连接断开,就会被 close,你得立刻感知。
- 续租逻辑必须单独起 goroutine,和 leader 主循环解耦
- 用
for range读这个 channel,一旦range结束(channel close),立刻触发Resign()或标记状态为非 leader - 别把续租逻辑塞进 leader 循环里 —— 如果主循环卡住(比如 DB 查询慢),续租就停摆
真正难的不是抢到 leader,而是随时准备被取代。所有状态检查(IsLeader())、所有业务执行前的防护、所有对 Observe/KeepAlive channel 的响应,都得当成常态逻辑写进主干,而不是“异常处理分支”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











