什么是 Dubbo 的优雅停机,怎么实现的?
结论先行:优雅停机是“先摘流量、再等在途请求跑完、最后关资源”的有序退出。Dubbo 通过 JVM 关闭钩子(ShutdownHook)触发:注销注册中心地址 → 等待通知传播 → 停止接收新请求 → 等待在途请求结束(默认 10 秒)→ 关闭线程池与连接。用
kill -9会跳过全部步骤。
一、六步退出流程
SIGTERM / Spring close
│
▼
① 反注册:从注册中心删除本机地址(临时节点会话结束也会删)
│
▼
② 传播等待:给消费端一点时间收到变更通知(通常 1~3 秒)
│
▼
③ 拒绝新请求:协议层标记关闭,新请求直接失败或重定向
│
▼
④ 等待在途:等在处理中的请求完成,超时上限 shutdown.wait
│
▼
⑤ 关线程池、关连接、清缓存
│
▼
⑥ 退出 JVM
二、最容易踩的坑
| 坑 | 后果 | 处理 |
|---|---|---|
用 kill -9 停进程 | 钩子不执行,在途请求全部失败 | 用 kill -15,容器里配 STOPSIGNAL SIGTERM |
| 注册中心通知有延迟 | 地址已删但消费端还在打 | 反注册后 sleep 2~3 秒再停 |
| 提供者先关、消费端后关 | 消费端调用报错 | 先停消费端,再停提供端 |
| 长耗时任务不受控 | 超时被强杀 | 拆分异步任务,或调大 shutdown.wait |
| 容器滚动更新瞬间切流 | 新老 Pod 同时不可用 | 配 preStop 钩子 + 就绪探针 |
三、配置与实现
Dubbo 侧:
dubbo:
service:
shutdown:
wait: 10000 # 等待在途请求的毫秒数
provider:
delay: 0 # 延迟暴露,避免未就绪就接流
Spring Boot 侧要保证钩子先于连接池关闭执行:
server:
shutdown: graceful # Web 容器也走优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 20s
Kubernetes 侧:
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
terminationGracePeriodSeconds: 30
sleep 5 给 Service 摘除 Endpoint 与注册中心通知留出时间,terminationGracePeriodSeconds 必须大于 preStop 加 shutdown.wait,否则会被 SIGKILL。
四、验证是否真的优雅
压测中执行 kill -15,观察三件事:
1. 注册中心里该实例是否在 1~3 秒内消失
2. 消费端日志是否只有“地址变更”,没有 RpcException
3. 提供端日志里在途请求是否都打印了完成
常见追问
- 追问:为什么不能只依赖注册中心的临时节点删除?会话超时依赖心跳,最长可能几十秒,期间流量仍在打向已死实例,所以必须主动反注册。
- 追问:
shutdown.wait设多大?取 P99 耗时加余量,通常 5~10 秒;设太大拖慢发布,设太小会截断长请求。 - 追问:消费端需要优雅停机吗?需要。消费端要等在途响应回来、把未发送的请求处理完,否则同样会产生超时错误。
小结
- 顺序是“摘流量 → 等传播 → 拒新请求 → 等在途 → 关资源”。
- 必须用 SIGTERM,容器要配
preStop和足够的宽限期。 - 发布顺序:先停消费端,再停提供端。
- 优雅停机的效果要用压测验证,不能只看配置。