平滑无损发布需四要素协同:注册时机晚于资源初始化、下线前先反注册再关闭监听器、http/grpc连接draining、客户端必须配置重试。kratos不自动兜底,须开发者显式控制各环节契约。

服务启动时注册时机必须晚于资源初始化完成
常见错误是服务一启动就向注册中心上报实例,但此时数据库连接池、缓存客户端、gRPC 客户端等还没 ready,导致刚注册就被流量打挂。Kratos 本身不自动控制注册时机,得靠开发者显式协调。
实操建议:
- 在
main.go中不要直接调用registry.Register,而是封装一个startService函数,内部按顺序:初始化 DB → 初始化 Redis → 启动 HTTP/gRPC Server → 最后才调用registry.Register - 使用
health.Checker接口实现自定义健康检查,把资源 readiness 状态暴露为/health端点,供 Istio 或 K8s liveness probe 调用 - 若用
contrib/registry/nacos或contrib/registry/etcd,注意其Register方法默认不带重试和 backoff,建议包装一层带指数退避的注册逻辑
服务下线前必须先从注册中心反注册再关闭监听器
直接 kill 进程或调用 http.Server.Shutdown 后再反注册,会导致注册中心元数据残留数秒到数十秒(取决于心跳间隔),消费者仍会路由请求过去,报错 Connection refused 或 No instance。
实操建议:
- 在
signal.Notify捕获SIGTERM后,立即执行registry.Deregister,成功后再调用http.Server.Shutdown或grpc.Server.GracefulStop - 反注册失败不能跳过,应加日志并等待最多 3 秒重试,避免“假下线”
- Kratos 的
app.Run默认不处理优雅退出,需手动接管生命周期:用app.WithServer注册服务,再用app.WithBeforeStop和app.WithAfterStop插入反注册与资源释放逻辑
HTTP/gRPC 层需支持连接 draining,避免正在处理的请求被中断
Kratos 底层用的是 Go 标准库的 http.Server 和 grpc.Server,它们都支持 graceful shutdown,但默认行为不够激进——新连接拒绝了,但已建立的长连接(如 streaming、keep-alive)可能还在传数据,shutdown 超时后会被强制断开。
实操建议:
- 给
http.NewServer配置http.Server.ReadTimeout和WriteTimeout,避免单个慢请求拖垮整个 shutdown 流程 - 对 gRPC 服务,启用
KeepaliveParams并设置合理的MaxConnectionAge,让客户端主动重连,减少 shutdown 时待 drain 的连接数 - 在
app.WithBeforeStop中,可调用http.Server.Close(非 Shutdown)来快速终止监听,再等所有活跃连接自然结束,适用于对一致性要求不高、但希望更快释放端口的场景
客户端需容忍短暂的服务发现延迟与实例抖动
即使服务端做到了完美上下线,客户端若用本地缓存(如 contrib/registry/nacos 的内存缓存)、未配置重试或超时太短,仍会感知到抖动。Kratos 的 resolver 不自动 fallback,出错即抛异常。
实操建议:
- 所有远程调用必须包裹
retry中间件,至少配置 2 次重试,间隔 100ms 起步,避免因一次注册中心同步延迟就失败 - 在
grpc.Dial时启用WithBlock+WithTimeout,防止阻塞在 resolver 阶段;同时设置WithDefaultServiceConfig指定 DNS 解析策略,例如{"loadBalancingConfig": [{"round_robin": {}}]} - 避免在业务代码里硬编码
discovery:///xxx.service,应通过配置中心注入,便于灰度期动态切换服务发现地址











