etcd选举稳定性依赖Lease与KeepAlive机制,租约需设10~15秒并持续续租,防脑裂须用事务CompareAndDelete+Put,watch需独立goroutine带jitter重连,严禁缓存isLeader状态。

etcd 的 Lease 和 KeepAlive 是选举稳定性的关键
不靠心跳续租的租约,选主节点会在网络抖动后立刻失联。etcd 选举本质是「谁持有有效租约 + 谁在 key 上成功创建 ephemeral node」,而租约失效不是由客户端主动释放,而是由 etcd server 在 lease 过期后自动清理关联的 key。
-
Lease.Grant(ctx, ttl)中的ttl建议设为 10~15 秒,太短会因 GC 或调度延迟误触发重选;太长(如 60s)则故障发现慢 - 必须用
Lease.KeepAlive(ctx, id)启动一个持续续租协程,且要消费其返回的—— 不消费会导致 context cancel 后续租约静默失效chan *clientv3.LeaseKeepAliveResponse
- 别用
Lease.TimeToLive定期轮询判断是否过期:它只是快照,无法替代 keepalive 流式保活
用 CompareAndDelete + Put 实现安全的主节点迁移
单纯监听 leader key 变更(Watch)只能感知结果,无法防止双主。真正防脑裂的方式是在新节点上位前,先确认旧 leader 已失去租约绑定——这得靠 etcd 的事务 API。
- 选举时先
Grant租约,再用Put写入 leader key,并设置LeaseID绑定 - 当需要主动让位或降级,不要直接
Delete,而应构造 txn:Compare: [leaseid == old_id]+Then: [Delete],避免旧租约意外续上后被误删 - 监控侧若发现 leader key 存在但对应 lease 已过期(查
Lease.TimeToLive返回ErrLeaseNotFound),说明该节点已“幽灵存活”,需强制干预
clientv3.Watcher 的 cancel 时机决定故障收敛速度
Watcher 不是后台常驻服务,它依赖底层 gRPC stream;一旦 context 被 cancel,watch 流就断开,且不会自动重连。很多实现把 watch 放在 main goroutine 里,一崩全停。
- 务必用独立 goroutine 启动 watch,并捕获
ctx.Done()和err != nil两种退出条件 - 重连逻辑里要加 jitter(如 100ms~1s 随机延迟),否则集群重启时所有节点同时重试,压垮 etcd
- Watch 的
WithPrefix和WithRev很重要:前者避免漏掉同目录下其他候选节点变更;后者传上次 watch 到的resp.Header.Revision,防止事件丢失
本地状态与 etcd 状态不一致是最难 debug 的点
Go 程序里存了个 isLeader bool 变量,但 etcd 里 leader key 早已被别人抢走——这种错位不会报错,只会让业务逻辑静默出错。
- 绝不缓存 leader 身份到全局变量;每次关键操作前,用
Get(ctx, leaderKey)检查 key 是否仍存在且 value 匹配自身 ID - 在租约 keepalive 协程里监听
LeaseKeepAliveResponse.TTL == 0,这是 etcd 明确告知“你已被踢出”的信号,此时应立即置isLeader = false并停止对外提供服务 - 日志里每条「成为 leader」和「放弃 leader」都必须带
Revision和LeaseID,否则排查时根本分不清是哪次选举生效的
etcd 分布式选举不是开箱即用的功能,它把一致性边界交给了你:租约续期、watch 恢复、状态同步,每个环节断掉都会导致角色错乱。最危险的不是选不出来,而是你以为自己是 leader,其实 etcd 早就把你忘了。











