kratos 框架本身不支持开箱即用的平滑重启,关键在于开发者需显式控制注册时机、反注册顺序、连接 draining 和健康探针联动四个契约点。

Kratos 框架本身不提供开箱即用的“平滑重启”能力,所谓“重启失败”,本质是服务在 kill -15 信号下未能完成 graceful shutdown 流程,导致注册中心残留、连接中断、请求丢失。关键不在框架是否支持,而在开发者是否显式控制了四个契约点:注册时机、反注册顺序、连接 draining、健康探针联动。
为什么 Kratos 的 app.Run 默认无法平滑重启
Kratos app.Run 启动后直接阻塞,不接管 SIGTERM 信号,也不自动调用 registry.Deregister 或 http.Server.Shutdown。一旦进程被 kill,所有资源(DB 连接池、gRPC 客户端、HTTP server)都处于未通知状态就被终止,注册中心元数据延迟过期,消费者仍在发请求,报错 Connection refused 或 No instance。
- 默认
app.Run没有WithBeforeStop和WithAfterStop钩子,生命周期不可控 - contrib/registry/etcd 或 nacos 的
Register方法不带重试,注册失败静默,服务“假上线” - health 端点未绑定 readiness 状态,K8s
livenessProbe会误杀未 ready 的实例
必须手动注入的优雅退出链:Deregister → Shutdown → Close
不能依赖任何“自动兜底”,必须在 signal.Notify 捕获 SIGTERM 后,按严格顺序执行三步:
- 立即调用
registry.Deregister,失败需指数退避重试最多 3 秒(避免注册中心残留) -
Deregister成功后,再调用http.Server.Shutdown或grpc.Server.GracefulStop - 最后关闭 DB、Redis、gRPC client 等资源(放在
app.WithAfterStop) - 务必用
app.WithBeforeStop注入反注册逻辑,而非在main函数末尾写defer
HTTP/gRPC 层连接 draining 不够用?调参才是关键
Go 标准库的 Shutdown 默认只等 30 秒,且对 keep-alive 连接和 gRPC streaming 不敏感。若不干预,大量长连接会在超时后被强制断开,造成请求失败。
- 给
http.NewServer显式设置ReadTimeout和WriteTimeout(如 5s),防止单个慢请求拖垮整个 shutdown - gRPC server 启用
KeepaliveParams,并设MaxConnectionAge(如 30m),推动客户端主动重连 - 避免在
app.WithBeforeStop中直接调Shutdown,应封装带 context 超时的调用,例如ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
注册时机晚于资源初始化:最容易被跳过的一步
服务一启动就 registry.Register,但此时 redis.Client 还没 Ping 通、sql.DB 连接池尚未 warmup,注册后立刻被流量打挂——这不是重启失败,而是“刚上线就崩”,常被误判为重启问题。
- 删掉
main.go中裸调的registry.Register - 封装
startService(),顺序固定为:initDB→initRedis→newHTTPServer→newGRPCServer→ 最后registry.Register - 实现
health.Checker接口,把各组件Check()结果聚合到/health,供 K8sreadinessProbe使用 - 若用
contrib/registry/nacos,包装Register调用,加backoff.Retry,首次失败不 panic
真正卡住平滑重启的,往往不是某行代码写错,而是多个环节的等待窗口没对齐:注册中心心跳间隔 vs shutdown 超时时间 vs 客户端重试间隔。这些值全得人工对齐,Kratos 不替你做决定。











