微服务优雅下线本质是解决注册中心、客户端缓存、网络路由三者状态不同步导致的“轮询空投”问题;需协同注册侧(主动注销+权重归零)、客户端侧(缩短刷新+强制校验)、容器侧(prestop+draining)三端发力,并通过实时接口验证与抓包兜底确保下线干净。

微服务优雅下线时轮询流量“空投”,本质是客户端还在往已下线实例发请求,而负载均衡器没及时剔除它——不是没下线,而是“下得不够干净”。核心矛盾在于注册中心状态变更、客户端缓存刷新、网络路由更新三者不同步。解决需从注册侧、客户端侧、容器侧协同发力。
注册中心侧:主动摘除 + 权重归零
不能只靠进程退出自动反注册,必须前置干预:
- 在应用收到 SIGTERM 后,立即调用注册中心 API 主动注销自身,例如 Nacos:
curl -X PUT "http://nacos:8848/nacos/v1/ns/instance?serviceName=order-service&ip=10.244.1.5&port=8080&enabled=false" - 更稳妥的做法是先将实例权重设为 0(weight=0),让注册中心返回但不参与负载,再延时几秒后注销,给客户端留出缓冲窗口
- Eureka 用户可调用 /actuator/service-registry/status?status=DOWN 端点触发下线(需开启 service-registry 端点)
客户端侧:缩短缓存 + 强制刷新
轮询空投的主因是 Ribbon 或 Spring Cloud LoadBalancer 拿着过期的服务列表在转发:
- 调大客户端服务列表刷新频率,例如 Ribbon:
ribbon.ServerListRefreshInterval=5000(默认30秒,建议压到5~10秒) - 禁用本地缓存兜底逻辑(如某些 SDK 默认 fail-fast 会缓存最后一次成功列表),确保每次请求前都查注册中心或至少校验健康状态
- 对关键调用链,可在发起请求前加一层 instance.isAlive() 校验(需注册中心支持实时健康检查接口)
容器与网关侧:阻断最后一公里流量
即使注册中心已更新,K8s Endpoint、SLB 或 API 网关仍可能把请求打到 Terminating Pod:
- K8s 中为 Pod 配置 preStop hook:sleep 10~15 秒,确保 Endpoint Controller 和 kube-proxy 有足够时间更新 iptables/ipvs 规则
- 设置 terminationGracePeriodSeconds: 30,避免 SIGKILL 过早终结进程,导致未处理完的请求被丢弃
- 若使用 SLB 或 Nginx Ingress,开启其 优雅下线开关(如 SLB 的“连接 draining”功能),让存量长连接自然结束,新连接不再接入
验证与兜底:上线前必做两件事
光配置不够,要可观测、可验证:
- 下线过程中,实时 curl 注册中心接口(如 Nacos 的 /nacos/v1/ns/instances?serviceName=xxx),确认目标实例已不在列表或 weight=0
- 在客户端机器上抓包或查日志,确认是否还有对目标 IP:Port 的新建连接;若有,说明某层缓存或路由未生效,需回溯排查











