必须用session而非直接grant,因session封装自动续租、上下文绑定与争抢逻辑;正确流程是先newsession再newelection,campaign后监听observe通道并处理关闭,leader退出需显式resign而非仅defer。

etcd选举必须用Session,不是直接调Grant
很多人一上来就手动调用clientv3.Grant申请lease,再用clientv3.Put写key,以为这样就能做选主——结果发现多个节点同时认为自己是leader,或者leader切换时出现空窗期。根本原因是lease本身不带竞争语义,Session才是etcd官方封装的、带自动续期+上下文绑定+争抢逻辑的选举基础组件。
正确做法是:先创建clientv3.Client,再用clientv3.NewSession初始化一个Session实例,它会自动后台续租;之后所有选举操作都基于这个Session对象展开。
-
Session的LeaseID会被自动注入到后续Put请求中,且生命周期与Session强绑定 - 若节点崩溃或网络断开,
Session超时后lease自动过期,对应key被删除,其他节点才能争抢 - 不要复用同一个
Session在多个goroutine里做选举,每个节点应独占一个Session
用election.LeaderElector比手写CompareAndSwap更稳
etcd clientv3自带concurrency.Election包,提供election.NewElection和LeaderElector类型,封装了watch + CAS + lease续约全套逻辑。有人图省事自己用Txn写CAS判断,容易漏掉watch监听或误判Revision变化,导致脑裂。
标准流程是:election.NewElection传入Session和唯一路径(如/election/leader),然后调elect.Campaign提交候选值(通常是本机IP+PID),成功即成为leader;失败则自动watch该key,等当前leader释放后重试。
GoLand 2026.1.1 是 2026.1 发布后的首个维护修正版本,适合已经开始体验 2026.1 新功能并希望同步补丁的开发者。它更适合用于入门项目、现有项目迁移测试和 IDE 行为验证。
-
Campaign是阻塞调用,但内部已处理超时、上下文取消、lease续期失败等边界 - 返回的
election.LeaderElector对象可调ObservedLeader获取当前leader信息,用于健康检查 - 不要在
Campaign返回后还反复调用它——它本身已是长连接watch机制,重复调用会触发多次争抢
GoLand调试时context.DeadlineExceeded常因etcd地址配错或TLS没关
本地启动etcd用etcd --listen-client-urls http://localhost:2379 --advertise-client-urls http://localhost:2379即可,但GoLand里clientv3.Config若写了https://开头的Endpoints,又没配TLS字段,就会卡在连接阶段,最终报context.DeadlineExceeded。这不是代码问题,是配置和环境不匹配。
- 开发阶段优先用HTTP协议,
Endpoints设为[]string{"http://localhost:2379"},删掉TLS字段 - 如果必须用HTTPS,确保etcd启用了证书,且Go代码中
TLS字段填了tls.Config{InsecureSkipVerify: true}(仅限测试) - GoLand的Run Configuration里,检查
Working directory是否指向项目根目录——否则go.mod可能加载失败,间接导致依赖版本混乱,引发clientv3行为异常
Leader退出时必须显式调elect.Resign,不能只靠defer
很多代码在main函数末尾写defer elect.Resign(context.Background()),看似稳妥,但实际进程被kill -9或panic崩溃时,defer根本不会执行,lease残留,其他节点要等lease过期(默认10秒)才能重新选举,造成服务不可用窗口。
正确做法是:注册os.Signal监听os.Interrupt和syscall.SIGTERM,收到信号后主动调elect.Resign,并等待返回再退出。
-
Resign会主动删除leader key并释放lease,让其他节点立刻感知到变更 - 不要在
Resign前关闭etcd client——它需要client发请求,应放在client.Close()之前 - 如果程序有长时间运行的goroutine(比如HTTP server),确保
Resign完成后再waitGroup.Wait()或server.Shutdown()
etcd选举真正难的不是API调用,而是lease生命周期、watch响应时机、信号捕获顺序这三个点咬合得严丝合缝。少一个环节,就可能让集群在凌晨三点静默失联十分钟。










