主题
应用优雅上下线
一句话机制
优雅下线的本质是「先停进水、再放完水」:先摘监控、摘流量、停止接收新请求,再把已在途的请求处理完,最后释放资源退出。它靠层层递进的信号/钩子机制实现——从 OS 信号一路传导到 JVM、Spring、Dubbo。
「不优雅」的代价(反面清单)
- 停服务却没关监控 → 应用停了还疯狂报警。
- 停服务没通知调用方 → 大量调用失败。
- 线程执行到一半 JVM 进程被强杀 → 数据不一致。
- 服务还没准备好就对外提供 → 启动期大量失败调用。
分层的优雅停机机制
| 层 | 机制 | 要点 |
|---|---|---|
| OS | 信号 | kill -15(SIGTERM,可被处理,优雅)vs kill -9(SIGKILL,立即强杀,≈ 宕机) |
| Docker | stop/kill | docker stop = SIGTERM;docker kill = SIGKILL |
| JVM | shutdown hook | Runtime.addShutdownHook(Thread),JVM 正常关闭时回调 |
| Spring | bean 销毁 + 事件 | 容器初始化时注册 shutdown hook;DisposableBean.destroy() + ApplicationListener<ContextClosedEvent> |
| Dubbo | 优雅停机 | Provider 先标记不再接收新请求(新请求报错让 Client 重试)、等在途线程执行完、超时强关;Consumer 不发新调用、等未响应请求返回 |
不变量
- SIGTERM 可被处理、SIGKILL 不可被处理。想优雅停机,必须给进程发 SIGTERM(
kill -15/docker stop)而非 SIGKILL。 - 上层机制都建立在下层之上:Spring/Dubbo 的优雅停机底层都是 JVM shutdown hook,而 shutdown hook 只在「正常关闭」时触发——若被
kill -9强杀,一切钩子都来不及执行。 - 优雅下线的正确顺序:摘流量(摘监控、摘 Service/注册中心)→ 处理在途请求 → 释放资源 → 退出。
常见误解
- ❌ 「
kill -9快,用它关闭更省事」——错。SIGKILL 无法被捕获,等于模拟宕机,会丢失在途请求、跳过所有清理。 - ❌ 「
docker stop是强杀」——错。docker stop发的是 SIGTERM,给进程优雅关闭的窗口,超时(默认 10s)才会 SIGKILL。 - ❌ 「优雅下线 = 进程正常退出就够了」——错。不摘监控、不摘流量、不等在途请求,进程「正常退出」照样产生报警和失败调用。
关联
- 上游:优雅上下线
- 同域:Docker技术栈总览 · [Kubernetes Pod与探针机制](./Kubernetes Pod与探针机制)(K8s 删除 Pod 同样走「preStop hook → SIGTERM → 宽限期 → SIGKILL」)
- 域地图:运维与云原生