Consul 和 etcd 是 Golang 微服务最常用的注册中心:Consul 开箱即用、健康检查自动集成,适合多数场景;etcd 更轻量强一致,但需自行设计 key 结构和心跳逻辑,适合深度绑定 K8s 或高一致性要求系统。

Consul 和 etcd 是 Golang 微服务中最常用、最稳妥的两个注册中心选择,其中 Consul 开箱即用、健康检查自动集成,适合绝大多数业务场景;etcd 更轻量、强一致,但需自行设计 key 结构和心跳逻辑,适合已深度绑定 Kubernetes 或对一致性要求极高的系统。
用 hashicorp/consul/api 注册服务时,必须设好 ID 和健康检查参数
服务注册不是“写个地址就完事”,ID 必须全局唯一(建议拼接主机名 + 端口 ,如 "user-svc-192.168.1.10:8080"),否则重启后旧实例残留,导致调用失败。
健康检查不能只配 HTTP 路径,还要明确 Timeout 和 Interval,否则 Consul 会默认用 10 秒间隔轮询,而你的 /health 接口若耗时超过 2 秒就可能被误判为不健康。
-
DeregisterCriticalServiceAfter建议设为"30s":表示连续失败后最多保留 30 秒,避免雪崩式剔除 - 注册前务必确认 Consul agent 正在运行且网络可达,否则
ServiceRegister()会静默失败(错误被忽略) - 不要在
main()里注册完就直接http.ListenAndServe,应加time.Sleep(100 * time.Millisecond)确保注册请求发出
发现服务时别每次查 Health().Service,要缓存 + Watch
直接在每次 RPC 前调用 client.Health().Service("xxx", "", true, nil) 是典型反模式:既拖慢请求,又给 Consul 带来高并发压力。真实项目中应:
立即学习 “go 语言免费学习笔记(深入)”;
- 启动时拉取一次全量健康实例,存入内存 map 或 sync.Map
- 另起 goroutine 调用
client.Health().ServicePrefix或监听Watch(用api.QueryOptions.Wait避免轮询) - 收到变更后更新本地缓存,并触发负载均衡器重选节点
注意:Health().Service 返回的是 []*api.ServiceEntry,其中 s.Service.Address 是注册时填的地址,** 不是 Consul agent 的地址 **;如果服务跑在 Docker 或 K8s 内,这里必须填容器可访问的真实 IP 或 DNS 名,否则调用方连不通。
用 clientv3 接 etcd 时,租约续期失败不会 panic,但服务会悄无声息下线
etcd 不提供自动健康检查语义,一切靠租约(Lease)维持。常见陷阱是:
- 只调一次
Grant(),没做KeepAlive()—— 租约到期后 key 自动删除,服务“消失”但进程还在跑 -
KeepAlive()返回的chan *clientv3.LeaseKeepAliveResponse若未消费,goroutine 会卡住并泄漏 - key 路径设计不合理,比如用
/services/{name}/{uuid},导致无法用WithPrefix()批量查询
正确做法是:启动一个独立 goroutine 持续读取 KeepAlive() channel,一旦收到 nil(表示租约失效),立刻触发服务优雅退出或重注册逻辑。
别跳过服务注销,SIGINT 信号里必须调 ServiceDeregister 或删 etcd key
服务进程被 kill -15 终止时,若没主动注销,Consul 会等 DeregisterCriticalServiceAfter 时间才清理,期间流量仍会被打到已死节点;etcd 则完全依赖租约过期,延迟不可控。
标准做法是在 main() 中监听 os.Interrupt 或 syscall.SIGTERM:
sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) go func() {}()
这个环节最容易被忽略,但恰恰是服务发现链路中容错能力的关键一环——它决定了故障恢复时间(MTTR)是否可控。






























