etcd中服务下线通过绑定租约的key实现:写入时必须关联lease,客户端定期keepalive续租,租约过期后key自动删除,watch监听删除事件实现实时感知;禁用手动delete以保障故障自动下线。

etcd 里怎么表示“服务已下线”
服务下线不是删掉 key,而是让其他节点能快速、可靠地感知状态变化。etcd 本身不提供“服务生命周期”语义,得靠约定的 key 结构 + 带租约(lease)的 value 来表达。
-
put时必须绑定lease ID,不能用永久 key;否则进程崩溃后 key 一直残留,下游永远认为服务在线 - 推荐 key 格式:
/services/{service-name}/{instance-id},value 可存 JSON(如{"addr":"10.0.1.2:8080","version":"v2.3"}),但 value 内容不影响下线逻辑,关键在 lease 是否续期 - 客户端需定期调用
keepAlive续租;一旦停止续租(如进程退出、网络断开),etcd 自动回收 lease,对应 key 立即被删除——这才是“下线”的触发点
客户端如何及时发现服务下线
轮询 get 是低效且有延迟的。必须用 watch 机制监听 key 删除事件,这是唯一能接近实时感知下线的方式。
- watch 路径要带前缀,比如
/services/user-service/,这样实例增删都会触发事件 - 注意 watch 的
created_revision:首次连接时若设置start_revision过小,可能重放大量历史事件;建议用当前cluster_revision或略往前一点,避免积压 - watch 连接断开后必须重连并重新建立 watch,不能只依赖一次调用;etcd 客户端 SDK(如 go-etcd)通常封装了自动重连,但需确认是否启用
WithRequireLeader等选项,否则可能连到非 leader 节点导致漏事件
为什么直接 delete 不行
手动 delete 某个服务 key 确实能让 watcher 收到删除事件,但会破坏“故障自动下线”的前提——它把下线行为从“系统可靠性保障”降级为“人工操作”,完全违背分布式环境的设计目标。
- 运维误删、脚本执行错路径、权限配置错误,都可能导致服务被意外下线
- 无法应对进程 panic、OOM、容器被 kill 等非优雅退出场景;这些情况根本没机会执行
delete - 多个实例共用一个 lease ID 会导致“一死全下线”,而每个实例必须独占 lease 才能精准控制粒度
lease TTL 设多少才合理
TTL 不是越长越好,也不是越短越安全,得平衡探测灵敏度和网络抖动容忍度。
- 典型值设为 15–30 秒:足够覆盖大多数 GC STW、短暂网络分区,又能在 2 个心跳周期(默认每 5 秒续一次)内完成下线
- 如果服务启动慢或 GC 频繁,TTL 小于 10 秒就容易被误踢;观察 etcd server 日志里的
lease expired频次可辅助判断 - 不要全局统一 TTL;高可用核心服务(如 API 网关)可设 45 秒,边缘采集服务(如日志 agent)可设 10 秒——差异源于它们对“假下线”的容忍成本不同
真正难的是 lease 续期逻辑嵌入业务进程的稳定性:它得跑在主 goroutine 之外、不受 HTTP handler 阻塞影响、能感知 SIGTERM 并主动释放 lease。这点常被忽略,结果服务重启时旧 lease 还挂着,新实例启不来。










