Go服务Consul下线失败主因是反注册时机错误、未等连接结束、未停健康检查;必须Shutdown()返回后才执行ServiceDeregister(),且client.Health().Service()须设passingOnly=true。

Go 服务在 Consul 中“下线”失败,不是注册中心没响应,而是你没在退出前主动反注册 + 没等连接自然结束 + 没关掉健康检查心跳。
Consul 反注册必须放在 http.Server.Shutdown() 之后执行
很多人把 client.Agent().ServiceDeregister() 放在信号捕获后立刻调用,结果服务还没真正停 listen,Consul 就删掉了节点——下游还在往这个已 shutdown 但未 deregister 的实例发请求,直接报 connection refused。
-
Shutdown()是阻塞的,它只停止 accept 新连接,但会等已有连接自然关闭(比如正在处理的 HTTP 请求) - 必须等
Shutdown()返回后再调ServiceDeregister(),否则 Consul 里残留“假离线”节点,导致发现逻辑误判 - 别用
defer包裹反注册:defer 在函数 return 时才执行,而主 goroutine 可能早于 Shutdown 结束就退出了 - 示例顺序:
sigChan := make(chan os.Signal, 1)→signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)→<-sigChan→ 启 goroutine 执行srv.Shutdown()→srv.Serve()返回后 →client.Agent().ServiceDeregister(&consul.ServiceDefinition{ID: "xxx"})
client.Health().Service() 查询必须设 passingOnly=true
不加这个参数,client.Health().Service("user-service", "", false, ...) 会返回所有状态的服务实例,包括 critical 和 warning 的——这些节点其实已经挂了,但还没被 Consul 清理完,下游拿到它们就会失败。
-
passingOnly=true是硬性要求,它让 Consul 过滤掉非 passing 状态的节点 - 别依赖
client.Agent().Services():它只返回本机注册的服务列表,对服务发现毫无意义 - 如果服务刚 deregister,但下游缓存还没刷新,可配合
TagFilter做灰度隔离,比如只查tags=["v2"]实例 - Watch 机制要用阻塞查询 + Session,单纯轮询
Service()效率低且易漏变更
健康检查探针路径和间隔必须匹配服务实际能力
注册时写的 Check.HTTP = "http://127.0.0.1:8080/health",但服务还没启动完、或 /health 接口返回非 200,Consul 就会标记为 critical;反过来,Interval="1s" 会导致 Consul 集群被打爆,触发限流后健康检查全部失效。
立即学习“go语言免费学习笔记(深入)”;
-
/health必须真实存在、返回 200、且不依赖其他未就绪组件(比如 DB 连接池还没建好就返回 200,后续请求全崩) -
Interval建议设为10s起步,生产环境不低于5s;短于3s极易触发 Consul 的 rate limit - 别用
TTL类型健康检查:需要服务自己定时续租,出错概率高;优先选HTTP或TCP - 注册 ID 必须全局唯一,推荐格式:
"user-service-" + hostname + "-" + port,避免重启后覆盖旧实例
Docker 环境下 Consul 地址容易写成本地回环
本地开发用 http://127.0.0.1:8500 没问题,但 Go 服务跑在 Docker 容器里、Consul 跑在宿主机时,127.0.0.1 指向容器自身,根本连不上 Consul。
- Mac / Windows Docker Desktop:用
http://host.docker.internal:8500 - Linux Docker:用宿主机真实 IP,比如
http://192.168.1.100:8500(不能用docker0网桥地址) - 超时必须显式设置:
HttpTransport: &http.Transport{Timeout: 5 * time.Second},否则默认 30s 太长,注册失败感知滞后 - 别复用
http.DefaultClient:它没有定制超时,且可能被其他库污染
最常被忽略的是:优雅下线不是“让服务停”,而是“让流量先走完、再删节点、最后关自己”。任何一环断开,都会在 Consul 里留下幽灵实例,或让客户端拿到不可用地址。特别是反注册时机和健康检查配置,这两个点踩中一个,整个服务发现链路就不可靠。


















