etcd watch中断后不重连即为故障,因默认watchchan关闭后goroutine卡死在range循环中导致事件丢失;必须在循环外加select监听ctx.done()和重连信号,每次用context.withtimeout新建上下文,并将连接态、同步态、一致性态导出为prometheus指标实时监控。

etcd Watch 中断后不重连就是故障
Watch 断开本身不是问题,etcd 网络抖动、leader 切换都会导致流中断;真正要拦住的是“中断后没重建”。默认 cli.Watch() 返回的 watchChan 一旦关闭就不会再发事件,goroutine 会卡死在 for wresp := range watchChan 上,后续所有变更全丢。
必须把重试逻辑嵌进 watch 循环里:
- 每次启动 watch 前用
context.WithTimeout(ctx, 30*time.Second)创建新 ctx,避免复用已 cancel 的上下文 - 在
for循环外加select { case 分支,而不是只依赖 channel 关闭 - 收到事件后立刻更新本地
lastRev = resp.Header.Revision,下次 watch 显式带上clientv3.WithRev(lastRev + 1),防止跳变丢失 - 捕获
rpc error: code = Canceled desc = context canceled或send on closed channel后,立即log.Error并触发重建,不要等定时拉取兜底
配置解析失败不能静默吞掉
业务代码里对 json.Unmarshal() 或 yaml.Unmarshal() 没加 recover,一旦 key 里存了非法 JSON(比如少了个逗号),整个 watch goroutine 就 panic 退出,后续变更彻底失联——监控日志里只有一条 panic 记录,没人知道配置已经停更了 2 小时。
必须在 watch 回调里做防御:
- 用
defer func() { if r := recover(); r != nil { /* 记录错误 */ } }()包裹反序列化逻辑 - 失败时打
ERROR日志,明确包含key、revision和原始 value 片段 - 维护一个
failedKeys map[string]int,5 分钟内同一 key 失败超 3 次就主动告警(大概率是运维误改格式) - 暴露
/debug/config/failures接口返回最近 10 条失败详情,方便快速定位
本地缓存更新必须可验证
监控打点全绿,但业务读的还是旧配置——因为缓存没刷进去。常见原因是:回调里只更新了某字段,但结构体指针没透出去;或用了深拷贝但漏了嵌套 map;又或者并发写时没加锁导致部分字段被覆盖。
验证方式要轻量且无侵入:
- 每次成功解析后,把新配置的
hash(如fmt.Sprintf("%x", sha256.Sum256([]byte(jsonStr))))和revision写入一个全局atomic.Value - 健康检查端点
/health/config里读这个值,对比 etcd 当前head revision和本地 hash 是否匹配 - 不校验完整内容,只比 revision + hash,耗时控制在 1ms 内,不影响主流程
- 如果发现 revision 滞后 >5,或 hash 不一致,直接返回 503,让负载均衡器剔除该实例
连接态、同步态、一致性态必须暴露为指标
光看 etcd client 是否 connected 没用。真正要盯的是三件事:client 能不能发请求、本地缓存跟不跟得上、watch 流有没有在收事件。
这三类状态必须导出为 Prometheus 指标或 HTTP 健康检查:
-
连接态:用
cli.Get(ctx, "/health", clientv3.WithSerializable())快速探测,超时设 300ms,失败则etcd_client_up{job="xxx"} 0 -
同步态:通过
cli.Get(ctx, "/", clientv3.WithLastRev())获取 head revision,与本地 lastRev 比较,差值 >5 就打点config_sync_lag_revision{service="xxx"} 8 -
一致性态:统计每分钟
watchChan重连次数,连续 3 分钟 >10 次就触发告警,指标名etcd_watch_reconnect_total{service="xxx"} 12
revision 滞后和重连频繁这两项最容易被忽略,它们往往早于服务报错出现,是热更新失效的第一信号。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











