千万 QPS 规模下,串行重启慢得无法接受,盲目并行又会酿成事故。本文从单台优雅下线讲起,拆解分批并行、健康门禁与平台化的演进路径,帮你把大规模重启做得又快又稳。
周四晚上十点,某电商平台的核心网关集群需要一次全量重启。起因不复杂:一个连接池泄漏的修复补丁要生效,而这次修复涉及底层网络库的初始化逻辑,热加载走不通,必须重启进程才能生效。
集群有 4200 个实例。值班同学按老规矩跑了滚动重启脚本:摘一台,重启一台,等健康检查通过,再摘下一台。单台大约 40 秒,4200 台跑完,差不多 47 个小时。
没有人能接受一个持续两天的重启窗口。于是有人提议:干脆并行,一次重启 200 台。第一次尝试的结果是灾难性的:200 台实例同时下线又同时冷启动,剩余实例的流量瞬间上涨 5%,而冷启动回来的实例缓存是空的、连接池是空的、JIT 也没编译,处理延迟翻了四倍,把下游数据库的连接数打爆。错误率飙到 3%,整个集群差点被打挂,只能紧急暂停。
这个故事的尴尬之处在于:串行慢得不可接受,并行的第一次尝试又差点酿成事故。这一讲就来聊,千万 QPS 规模下的服务重启,是怎么从「一台一台排队」演进到「既大规模并行、又不出事故」的。
重启为什么是常态,而不是事故
先纠正一个常见的认知偏差:在小规模系统里,重启通常意味着「出事了」;但在千万 QPS 的系统里,重启是一种高频的例行运维动作,它的重要程度接近发布。
看一下千万 QPS 系统里那些非重启不可的场景:

你会发现这些场景有一个共同点:它们不等业务出问题才发生,而是主动治理的一部分。在千万 QPS 的语境下,「重启」早就是一个计划内的常规动作,而不是故障处理的手段。
但重启是有成本的,而且成本不低:
- 容量缺口:实例下线期间,它的流量要摊到其余实例头上,集群整体容量下降。
- 冷启动代价:新起来的进程缓存是冷的、连接池是空的、JIT 代码没编译,单实例处理能力可能只有稳态的三四成。
- 状态重建:本地缓存、会话数据、预热任务都要重新来一遍。
所以重启这件事的本质是:用一小段可控的风险窗口,换取系统长期运行的健康度。问题只在于,怎么把这个窗口压缩到足够短、把风险控制到足够小。
串行重启在千万 QPS 下为什么会崩
串行的账很好算。总耗时 = 实例数 × 单台重启耗时。
拿数字对比一下直观感受。百万 QPS 时代,一个服务可能只有 60 台实例,单台 40 秒,全量重启 40 分钟,下班前就能搞定,串行完全够用。同样单台 40 秒,换成千万 QPS 的 4200 台,就是 46.7 个小时。如果把单台优化到 15 秒,也要 17.5 个小时。也就是说,在串行模式下,无论怎么优化单台耗时,几千台实例的全量重启都逃不过「以天为单位」。
这里还藏着一个容易被忽略的放大因子:单台耗时并不是恒定的。串行重启到后期,集群里已经有一批实例下线了,剩余实例的负载更高,健康检查更慢,预热更吃力,单台耗时反而会上涨。越到后面越慢,这个毛病只有串行模式才有。
时间还只是表层问题,更深层的是三个:
第一,长窗口意味着长风险。重启进行到一半时,集群处于一种「半新半旧」的中间态:一部分实例是新版本,一部分是旧版本。如果新版本有问题,这个中间态持续得越久,排查和回滚的窗口就越混乱。47 个小时的中间态,几乎等于把半个集群长期暴露在未验证的状态里。
第二,串行假设了「一次只坏一台」。但真实世界的故障是批量发生的:一台宿主机挂了可能带走十几个实例,一个机房的网络抖动可能同时影响几百台。如果批量故障发生,串行排队重启,恢复速度根本追不上容量流失的速度。前面几讲聊过 RTO,串行的恢复速率是有天花板的,它决定了你在大规模批量故障面前的最坏 RTO。
第三,串行会拖慢整个技术团队的迭代节奏。安全补丁要等两天才能全量生效,泄漏修复要排队两天,发布窗口被压缩。久而久之,团队会倾向于「能不重启就不重启」,本该治理的问题被一直拖着,系统健康度反而往下掉,和重启的初衷正好相反。

所以结论很清楚:当实例数跨过某个量级,串行重启就从「慢一点但能用」变成「结构性不可用」。这个量级大概在几百台上下,取决于你对重启窗口的容忍度。
并行的前提:先把单台重启做对
很多人以为从串行到并行,就是把 for 循环改成并发执行。这是这条路上最常见的误区。如果单台重启本身是粗糙的,并行只会把粗糙放大几百倍。前面那次 200 台并行的事故,根因不是并行本身,而是单台重启太脆弱:下线不优雅、启动不预热,一放大就崩。
单台重启要解决两个问题:优雅地下线,温暖地启动。
先看优雅下线。一个实例下线时如果直接 kill 进程,那些正在处理中的请求就会被粗暴打断,客户端收到连接重置,用户看到报错。正确的做法是一套有序的退出流程:

这个流程里有几个坑。摘除流量之后,负载均衡的摘除不是瞬时生效的,各处的路由缓存、长连接复用都可能在几十秒内继续把请求送过来,所以要留一个缓冲期。等待存量请求时要有超时兜底,不能无限等,遇到长连接、长任务要强制收尾。关闭下游连接要温和,避免把压力甩给数据库。
再看温暖启动。进程起来不等于能扛流量,这是冷启动问题的核心。一个刚启动的 Java 服务,可能要经历这几个阶段才能到稳态:

对应的应对手段也很明确:
连接预建立:启动时主动和数据库、缓存、下游服务建连,而不是等第一个请求来了再建。
缓存预热:从远端加载热点数据,或者从同组健康实例同步本地缓存快照。
流量慢启动:刚回来的实例不要立刻接满流量,权重从低到高逐步爬坡,比如前十秒只接 10%,逐段升到 100%。
JIT 预热:对延迟敏感的服务,可以用预热请求把热点路径先编译掉,或利用应用自身的预热钩子。
这里要算清楚一笔账:单台重启的耗时和质量,是整个并行体系的下限。并行度加得再高,每台还是要走一遍下线和启动;单台从 40 秒优化到 15 秒,总耗时直接砍掉一大半,这个收益比单纯加并行度更稳、更安全。所以演进的正确顺序是:先打磨单台,再谈并行。
分批并行:用编排把并行关进笼子
单台做对之后,下一步才是并行。但千万 QPS 下没有人会做「全量一起重启」这种无脑并行,业界的标准答案是分批:批内并行,批间串行。

这个编排里有四个设计要点,每一个都是事故换来的。
第一,分组要按故障域,而不是随机。 同一个机架、同一个交换机下、同一个可用区的实例要尽量拆到不同批次里。否则一批重启恰好覆盖了某个机架的全部实例,这个机架再有点风吹草动,容量损失就是叠加的。分组的本质是让每一批的爆炸半径互相独立。
第二,批间必须设健康门禁。 每批重启完成、新实例接满流量之后,不要急着进下一批,先观察一段窗口:错误率、P99 延迟、下游依赖的成功率。任何一项超过阈值就自动暂停。暂停不是失败,而是编排系统替人踩了刹车。前面那次事故如果有门禁,第一批 200 台之后错误率抬升就会被拦住,不会继续往下走。
第三,批的大小是速度和安全的权衡。 批越大,总耗时越短,但单批出问题的影响面也越大。常见的经验起点是集群容量的 1% 到 5%,再结合容量余量调整。拿 4200 台的集群举例:每批 1%(42 台),单批下线时容量损失约 1%,剩余实例压力很小;如果每批 5%(210 台),总耗时能缩短到原来的五分之一,但任何一批出状况,都是 5% 的容量瞬间蒸发。怎么取舍,取决于你的门禁灵不灵、容量余量厚不厚。
这里有一个硬约束必须守住:任意一批全部下线时,剩余实例必须还能扛住全量流量,且留有余量应对流量尖峰。这要求集群平时就保持 N+1 甚至 N+2 的容量水位。换句话说,重启编排和容量规划是同一件事的两面,一个常年贴着容量红线运行的集群,连重启的资格都没有。
第四,暂停之后要能续跑。 一次 4000 台的重启,中间几乎必然会被门禁拦下一次两次,也可能赶上业务高峰需要主动让路。编排系统要支持在任意批次暂停、恢复、跳过、取消,而不是一停就要从头再来。
做到这四点,分批并行就把「一次重启 200 台的蛮力」变成了「有刹车、有方向盘的编排」。这是从串行到并行的第一道坎。
重启风暴:冷启动的集体踩踏
分批解决了「重启过程中集群会不会被打挂」的问题,但还有一个更隐蔽的问题:大量实例同时冷启动,会对外围依赖形成集体踩踏。这就是重启风暴。

三种风暴里,连接风暴最凶险。一台实例稳态时可能维持 200 个数据库连接,500 台同时重启再同时建连,就是 10 万个连接请求在几秒内砸向数据库,而数据库建连本身是重操作。很多重启事故的直接死因不是服务自己,而是把数据库打挂了。
对应的治理手段,按层次来看:
错峰。 批内的实例也不要同一微秒启动,加入随机抖动,比如批内按 0 到 30 秒随机延迟启动,把尖峰摊平。这是成本最低、收益最直接的一招。
限速建连。 连接池的重建要有速率控制,不要在进程启动瞬间全速建连。有些中间件客户端支持启动期连接数爬坡,没有的话就在网关或代理层排队。
预热前置。 缓存预热在挂载流量之前完成,预热期间实例对下游的访问是可控的、批量的,比接了流量之后被动穿透要温和得多。
下游的自保。 站在数据库和缓存的视角,也要有准入控制:连接数突增时排队而不是硬接,必要时优先保住存量连接。防御不能只靠重启方自觉。
还有一个经常坑到人的联动问题:重启不要和上下游的其他变更叠加。如果下游数据库正在做主从切换,或者上游正在发布,这时候重启自己的集群,几个变更叠加,出了问题连归因都困难。变更管理上把重启和发布、切换放在同一个日历里排期,是成熟团队的标配。
有状态服务与核心链路:并行要差异化
前面的讨论默认了服务是无状态的,摘下来重启就行。但千万 QPS 的系统里总有有状态的角色:队列消费者、分布式缓存节点、会话粘连的接入层、有本地磁盘状态的服务。这些角色的重启不能套用无状态的模板。
有状态服务重启的额外动作,核心是「状态的交接」:
队列消费者:先暂停消费、等消息处理完或转移位点,再重启,避免消息重复消费或丢失。
缓存节点:重启会导致这个节点上的热数据全部失效,如果缓存集群没有副本,就要考虑分批重启时数据的再平衡,或者提前把热数据迁移/复制到其他节点。
会话服务:先把会话迁走或持久化,再重启,否则一批用户直接掉线。
核心链路和非核心链路也应该有不同的重启策略。核心链路(交易、支付)用更小的批、更严的门禁阈值、更长的观察窗口;非核心链路(日志处理、离线任务)可以更激进,甚至直接整体重启。重启策略的激进程度,应该和这条链路出错的代价成反比。
另外一个实操经验:给重启定优先级,避开业务高峰。多数公司会把全量重启安排在业务低谷,但千万 QPS 的系统往往没有真正的低谷,只有相对低谷。这时候拼的就是对流量曲线的理解:选全天最低的窗口,批间观察跟着流量走,高峰期自动暂停让路。
从脚本到平台:重启能力的演进
回头看整条演进路线,其实和这个系列反复出现的一条主线一致:能力从人手里,逐步沉淀到系统里。

第一阶段是手工和脚本:能跑,但串行慢、无门禁、出错靠人盯。百万 QPS 时代的标配。
第二阶段是分批编排:批内并行、批间门禁、自动暂停。解决了「敢不敢并行」的问题,但策略是静态的,批大小、观察窗口都是人拍的。
第三阶段是平台化和自适应:重启被声明成一条策略,平台根据实时指标动态调整。集群健康度高、容量余量大,就自动加大批;下游压力抬升,就自动缩小批甚至暂停。重启和容量规划、变更管理、监控告警打通:重启前自动核对容量水位,重启中自动关联错误率变化,重启后自动生成报告。到这个阶段,重启才真正变成一个「随时可以做、做了不出事」的日常操作。
三个阶段的差异,可以整理成下面这张表:

这三个阶段不是「旧方案被淘汰」的关系,而是层层叠加:平台化的底层,依然是优雅下线、预热、分批、门禁这些基本功。跳过任何一层直接上平台,平台也只是一层皮。
判断一个团队的重启能力在哪个阶段,有个简单的试金石:敢不敢在工作日下午的业务时段,对核心集群发起全量重启。敢,说明门禁、预热、容量、回滚每一项都过硬;不敢,说明还有环节没闭环。
重启速度是系统健康度的体温计
把这一讲的内容收拢一下:服务重启从串行走到并行,表面上是执行方式的改变,实质是四件事的层层叠加。顺序不能乱:先把单台重启做优雅、做温暖,再用分批和门禁把并行关进笼子,然后用错峰和限速化解冷启动风暴,最后把策略沉淀进平台,让它跟着指标自适应。
这条演进路线也点破了千万 QPS 和百万 QPS 的一个根本差异:在百万 QPS,重启是一个「操作」,靠一两个熟练的工程师就能兜住;在千万 QPS,重启是一个「系统」,它要求容量、监控、编排、中间件、下游防御多方协同,任何一环缺位,并行就会变成事故。
留一个问题给你:你们团队全量重启一次要多久,中间要几个人盯着?这两个数字的改善空间,往往就藏在单台重启的耗时和门禁的自动化程度里。如果重启还停留在「熬夜盯脚本」的阶段,也许这一讲的演进路线,就是现成的改造清单。