Consul反注册必须在http.Server.Shutdown()返回后、os.Exit(0)前执行;早了导致流量丢失,晚了留下幽灵实例;需校验注册成功、设超时重试、recover防护,并手动关闭gRPC/DB/Kafka等组件。

Consul反注册必须在http.Server.Shutdown()返回之后执行
早于Shutdown()调用,Consul里服务记录就消失了,但HTTP server还在收请求,流量直接丢失;晚于os.Exit(),进程已死,deregister请求根本发不出去,留下幽灵实例。唯一安全窗口就是srv.Shutdown()返回后、os.Exit(0)前。
常见错误是把client.Agent().ServiceDeregister()写在defer里——panic时不一定执行,kill -9则完全跳过。信号捕获后,必须显式控制执行顺序:
- 用
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)监听,且只注册一次 -
srv.Shutdown()放进goroutine,避免阻塞主流程 - 主goroutine等待
srv.Shutdown()返回,再立刻调client.Agent().ServiceDeregister() - 注销失败不阻塞退出,但必须
log.Error(err)留痕
反注册要带重试、超时和存在性校验
注册成功 ≠ Consul真正写入:ACL权限不足、service ID冲突、网络抖动都可能导致静默失败。如果没验证就直接deregister,会报404 Service not found,日志全是无意义错误。
稳妥做法分三步:
立即学习“go语言免费学习笔记(深入)”;
- 注册成功后,把
service.ID缓存到本地变量或sync.Once初始化的全局字段 - 注销前先调
client.Agent().ServiceByID(serviceID),返回nil就跳过deregister - 反注册时设
cfg.HttpClient.Timeout = 1500 * time.Millisecond,外层包recover()防panic,最多重试3次,每次间隔200ms
Shutdown()只是起点,gRPC/DB/Kafka必须手动关
http.Server.Shutdown()只停HTTP连接,对gRPC、Kafka消费者、定时器、后台goroutine完全无效。只关HTTP,等于半关机:gRPC还在收流,Kafka还在拉消息,DB连接池卡在db.Close()等idle连接归还——但它不等正在执行的query,handler若没响应req.Context().Done(),就会死锁。
必须显式关闭所有组件:
- gRPC server用
grpcServer.GracefulStop(),不是Stop() - Kafka消费者(如sarama)调
consumer.Close(),并确认ConsumePartition循环已退出 - 所有后台goroutine通过
context.Context控制,shutdown前cancel(),内部检测ctx.Done()后退出 -
db.Close()前确保handler已进入shutdown流程,不再发起新query
Consul集群状态不稳时,注册和注销都会失效
90%的“服务治理失效”问题不在Go代码里,而在Consul集群本身。别急着改client.Agent().ServiceRegister(),先查集群健康:
-
consul members输出必须全为alive,出现failed说明gossip通信断了 - 启动命令必须显式指定真实内网IP:
-bind=10.0.1.10,不能只写0.0.0.0或127.0.0.1 - 防火墙必须放行8300(RPC)、8301(LAN gossip)、8302(WAN gossip)端口——只开8500不够
- Go客户端
Address应始终指向本地agent:"http://127.0.0.1:8500",不要直连远端server
最易被忽略的是:注销失败后,Consul集群可能已失联,但你的服务还在尝试重试——这会拖慢整个退出流程。此时应立即降级,比如往KV写个service/<id>/graceful_shutdown_fail</id>标记,供后续人工核对。


















