优雅停机与流量无损发布实战:别再让重启断掉用户连接

去年一个电商客户,大促期间发了个版本。发布脚本很简单:kill -9 老进程,启动新进程。结果发布那几秒,正在下单的用户连接全断了,支付到一半的直接失败。
客服电话被打爆。用户骂:“付了钱,订单没了?”
技术负责人说:“我们就重启了一下,怎么会这样?”
这是对停机最大的误解:你重启了,但用户还连着。
今天聊聊优雅停机与流量无损发布。不是那种“重启要小心”的废话,而是帮你理清楚:怎么停服务才不会断用户连接?怎么发新版才能不掉请求?
01 为什么直接重启会断连?
一个正在运行的进程,背后可能有几百上千个活跃连接。每个连接上,都可能有正在处理中的请求。
kill -9 是直接杀掉进程,不给任何清理时间。进程里的所有连接被操作系统强制关闭,客户端收到“连接被重置”。
结果:用户正在付款、上传文件、发评论,突然断了,体验极差。
反常识观点:停服务不是关掉进程,是让进程自己清理后退出。
02 优雅停机:给进程留时间
优雅停机的核心:先通知进程“准备停机”,给它时间处理完手上的活,再退出。
步骤一:发送 SIGTERM,不是 SIGKILL
SIGTERM(信号15):告诉进程“请优雅退出”。进程可以捕获这个信号,做清理工作。SIGKILL(信号9):直接杀掉,不给任何机会。用于最后手段。
步骤二:进程做三件事
停止接收新请求。从负载均衡摘掉自己,或者关闭监听端口。
等待现有请求完成。给正在处理的请求一个宽限期(grace period)。
超时强制退出。如果宽限期过了还有请求没完,记录日志,然后退出。
Kubernetes 中的配置:
terminationGracePeriodSeconds:给 Pod 多少秒清理时间。默认30秒。preStopHook:在 Pod 停止前执行的命令。可以用来通知负载均衡摘节点、等待连接排空。
示例:
yaml
spec: containers: - name: myapp lifecycle: preStop: exec: command: ["sleep", "10"] terminationGracePeriodSeconds: 30
03 连接排空:让负载均衡放人
进程已经停止接收新请求了,但负载均衡可能还不知道,还在往这个节点发流量。
需要连接排空(Connection Draining):
负载均衡检测到节点要下线,停止发送新请求
但已有的连接继续保留,等它们自然结束
超过排空时间,强制关闭剩余连接
AWS NLB/ALB:目标组可以设置“连接排空超时”,默认5分钟。
K8s Service:Pod 停止时,endpoint controller 会从 Service 中移除该 Pod。但已有的连接不会断,由应用层处理。
04 流量预热:新节点别接全量流量
新节点刚启动时,JVM 没预热、缓存没命中、连接池是空的。这时候接全量流量,大概率超时。
流量预热(Traffic Warm‑Up):新节点启动后,只接少量流量,逐步增加到正常水平。
K8s 中的实现:
readinessProbe:Pod 准备好后才加入 Service。可以配置初始延迟,给预热时间。
蓝绿部署:新环境先接小流量验证,再切全量。
金丝雀发布:先放1%流量,慢慢加。
那家电商后来改进后的流程:
新版本部署,readinessProbe 配置初始延迟30秒(给JVM预热)
readinessProbe 成功后,新Pod加入Service
旧Pod收到 SIGTERM,sleep 10秒等待连接排空,然后退出
整个过程用户无感知
05 常见坑与解法
坑一:preStop 设了,但没执行
检查:Pod 被删除时,preStop 只在新 Pod 还没 Ready 时执行?不对,preStop 在 Pod 终止时执行,无论新 Pod 状态。但 K8s 删除 Pod 时,会先发 SIGTERM,preStop 会在 SIGTERM 之前执行。
坑二:terminationGracePeriodSeconds 设太短
给30秒,但数据库连接池清理需要40秒。时间到了,强制 kill。解决方案:调大,或者优化清理逻辑。
坑三:负载均衡排空超时比进程宽限期短
负载均衡5分钟排空,进程30秒退出。进程退了,但负载均衡还在等连接,没用。应该让排空超时 ≤ 进程宽限期。
06 一个真实案例:从断连到无感
那家电商,之前发布必断连。我们帮他们改造了发布流程:
第一步:改重启信号
从 kill -9 改成 kill -15(SIGTERM)。应用代码捕获信号,关闭监听,等待现有请求完成。
第二步:加 preStop Hook
在 K8s 里配置 preStop:sleep 10,让负载均衡有时间摘掉 Pod。
第三步:配置负载均衡连接排空
AWS NLB 设置排空超时60秒。
第四步:加 readinessProbe
新 Pod 启动后,延迟30秒才接流量。给 JVM 预热、缓存加载。
改造后,下一次大促发布,用户无感知。运维负责人说:“以前发布像拆弹,现在像换灯泡。”
写在最后
优雅停机这件事,技术上不难,但容易被忽略。很多人把精力花在发布流程上,忘了发布那几秒,用户还在用。
那家电商的运维负责人后来总结了一句话:“以前觉得发布就是换版本,现在觉得,发布是换版本的时候用户还能用。”
你的发布,断用户连接吗?