consul服务注册失败主因是agent未运行、网络不通或go客户端配置错误;需显式设scheme="http"、检查leader、容器中bind地址非127.0.0.1;ttl健康检查须手动调updatettl()续期;服务发现为空需核对服务名大小写、dc和passing状态;watch()必须stop()防goroutine泄漏。

Consul服务注册失败:golang client.Dial() 返回 rpc error: code = Unavailable
根本原因通常是 Consul agent 未运行、网络不通,或 Go 客户端配置了错误的地址/协议。Consul 默认监听 127.0.0.1:8500,但 Go SDK 的 consul.NewClient() 默认尝试 HTTP + TLS,而本地开发环境一般跑的是 HTTP 明文。
- 检查 Consul 是否真在运行:
curl -s http://127.0.0.1:8500/v1/status/leader应返回类似"127.0.0.1:8300"的值 - Go 初始化时显式禁用 TLS:
cfg := consul.DefaultConfig() cfg.Address = "127.0.0.1:8500" cfg.Scheme = "http" // 必须设,否则默认用 https client, _ := consul.NewClient(cfg)
- 容器或云环境注意:Consul server 的
bind地址不能是127.0.0.1,需设为0.0.0.0或具体内网 IP,且防火墙放行8500(HTTP API)、8300(RPC)端口
服务健康检查不触发 deregister:用了 Check.TTL 却没调 client.Agent().UpdateTTL()
TTL 检查不是自动续期的——它靠客户端主动“心跳”。只注册一次服务,后续不调 UpdateTTL(),Consul 就会在 TTL 超时后直接剔除服务,不会进 critical 状态。
- 必须在业务逻辑中定期调用:
client.Agent().UpdateTTL("service:myapp", "", "passing") - 别把
UpdateTTL()放在 HTTP handler 里等请求触发——冷服务会掉;建议起独立 goroutine,间隔略小于 TTL(如 TTL=30s,每 25s 更新一次) - 如果用的是
Check.HTTP,就不用手动心跳,但要注意 HTTP 检查路径必须返回 2xx,且 Consul agent 能从自身网络访问该地址(比如容器内服务地址不能写localhost)
服务发现返回空列表:client.Health().Service() 总是 nil 或 []
常见于过滤条件写错、服务没真正通过健康检查、或查询时用了错误的数据中心名。
- 确认服务已注册且状态是
passing:curl "http://127.0.0.1:8500/v1/health/service/myapp?passing" - Go 调用时传参要严格:
client.Health().Service("myapp", "", true, &api.QueryOptions{Datacenter: "dc1"})—— 第二个参数是tag,空字符串表示不限 tag;第三个参数true表示只返回 passing 实例 - 多数据中心场景下,
Datacenter必须和目标服务所在 DC 一致;不填则默认用 client 初始化时配置的 DC,容易查错地方 - 注意:Consul 的服务名区分大小写,
"MyApp"和"myapp"是两个服务
goroutine 泄漏风险:用 client.KV().Watch() 监听配置变更没关 channel
Watch() 内部启 goroutine 轮询,返回的 chan *api.KVPair 不 close 会导致 goroutine 永驻。尤其在微服务频繁启停时,内存和连接数会持续上涨。
立即学习“go语言免费学习笔记(深入)”;
- 务必在不再需要监听时显式关闭:
watcher.Stop()(watcher是client.KV().Watch()返回的api.Watcher实例) - 不要直接用
for range watchCh无限读——一旦 channel 关闭,循环退出,但 watcher 还活着;正确做法是用watcher.Watch(ctx)配合 context 控制生命周期 - 如果只是启动时读一次配置,别用
Watch(),改用client.KV().Get()更轻量










