稳定性治理先做容量评估、慢链路梳理还是告警分层?
这三件事都重要,但真正的先后顺序往往不是在会里排出来的,而是在一次事故里被逼出来的:先找到那条真实拖垮现场的慢链,告警才知道该按哪一层重做,容量边界也才算得出来。
那次把顺序逼出来的事故,发生在一个周三晚上。
19:07,结算页 confirmOrder 的 p95 从 180ms 抬到 1.4s。
19:09,客服开始收到“优惠明细转很久,订单提交不出去”的反馈。
19:11,网关 504 冒头。
19:13,值班群已经吵成三条线:
- 平台同学说先看容量,活动流量比平时高了三成多。
- 值班负责人说先把告警收成 P1、P2,不然满屏都是红点,谁也看不清。
- 业务研发说别先谈治理动作,先把最慢的那一段找出来。
最后前两种声音先占了上风。
原因也不难理解。
团队在事故前两周刚做过一轮稳定性治理启动:
- 容量评估已经出了压测报告。
- 告警也刚按 P1、P2、P3 重新分过一遍。
- 只有慢链梳理还停在“知道依赖关系”,没有拉出一条真正的变慢传导线。
所以现场一出事,大家的直觉非常自然:要么先扩一把,要么先把告警墙收干净。
可真正把顺序打出来的,偏偏是这两件事都没把现场救下来。
先出现的症状,其实已经在暗示“不是平均容量不够”
那天最早坏掉的,不是整个站点,而是结算链里“查最优优惠”这一段。
按分钟回放,前 10 分钟的画面很清楚:
- 19:07,
confirmOrderp95 从 180ms 升到 1.4s,购物车、商品详情还正常。 - 19:08,
order-service自身 CPU 只有 54%,GC 没异常,但调用promotion-service的 client RT 开始抬头。 - 19:10,
promotion-service的重试倍率从 1.02 涨到 1.34。 - 19:11,网关 504 出现,超时率从 0.2% 涨到 3.8%。
- 19:12,
coupon-rule-service的连接池 pending 开始连续堆高。 - 19:14,订单线程池 queue 也跟着上来,结算页全面变慢。
这组现象后来回头看,已经很像一条慢链在往外传。
但在当时,它先被解释成了另一件事:活动流量把系统顶到了容量边缘。
因为从表面上看,这个判断很合理:
- 活动流量确实比平日晚高峰高 37%。
- 事故前的压测报告里,结算链路的安全区间本来就离这次峰值不远。
- 历史上也出现过“流量一上来,扩 Pod 就恢复”的情况。
于是第一反应先落在了容量上。
团队第一反应为什么会先错做容量
19:14 到 19:19,现场连续做了三件典型的“容量先手”:
order-service从 16 个 Pod 扩到 24 个。promotion-service从 12 个 Pod 扩到 18 个。coupon-rule-service背后的 MySQL 连接上限从 600 提到 900。
这套动作的逻辑很直白:
- 先把入口和中间层摊薄。
- 再把数据库连接打宽。
- 如果真是流量顶满,扩完以后单机压力和等待时间应该一起回落。
结果 5 分钟后,最该下来的东西没有下来。
扩容之后确实有两项指标变“好看”了:
order-service单 Pod CPU 从 54% 降到 31%。promotion-service单 Pod QPS 被摊薄了。
但真正代表现场有没有被救回来的指标,几乎没动:
confirmOrderp99 还在 3s 左右。- 网关 504 还在持续增加。
- 新拉起来的 Pod 在 90 秒内也迅速变慢。
coupon-rule-service连接池 pending 不降反升。
更扎心的是,数据库 QPS 还因为扩容后回源请求更多,短时间又抬了一截。
这就是第一条反证。
如果问题真是平均容量不够,扩容应该先让单次请求等待变短。可这次只是把 CPU 摊薄了,没有把每个慢请求变快,反而把同一条回源链上的压力继续送进下游。
也就是说,团队先做的不是完全错误的动作,但它只碰到了表层负载,没有碰到那条真正把请求拖慢的链。
容量没救场后,团队又为什么会先错做告警分层
扩完没起色,值班群马上滑向第二种直觉:先把告警墙收一收。
因为那时屏上已经同时在响:
- 网关 504
- 结算超时率
- 订单线程池 queue
promotion-serviceclient timeoutcoupon-rule-servicepending- cache miss 升高
- MySQL RT 抬头
- 重试倍率上升
告警太多时,人的本能就是先缩成几个层级,不然谁也不敢说第一轮该盯哪块。
于是 19:20 左右,值班负责人把主屏临时切成两组:
- P1:网关 504、结算超时率、核心 SLA
- P2:线程池 queue、连接池 pending、cache miss、重试倍率
这套分法在“安静一点”这件事上是有效的。
屏变干净了,群里的讨论也统一到“先看 P1”。
可 6 分钟过去,现场还是没有变快。
原因也很直接。
P1 上那些最红的信号,几乎全是结果层:
- 它们能证明用户已经受影响。
- 能证明事情在扩大。
- 却不能告诉你最早分叉的是哪一段。
更糟的是,最早开始移动的几项信号反而被放进了 P2:
coupon-template-cache命中率从 97% 掉到 63%promotion-service -> coupon-rule-service的 client RT 先抬头coupon-rule-service连接池 pending 比 504 早了将近 3 分钟
这就形成了第二条反证。
如果先把告警分层就足够救场,那么主屏变干净之后,诊断应该更快收敛。可这次主屏只是更安静了,判断却没有更靠近起点,大家只是更整齐地盯着结果层发红。
所以这一步也没把现场救回来。
它只是证明了一件事:
告警再怎么分层,如果没有先知道“系统是沿哪条链在变慢”,分出来的仍然可能只是更好看的结果层和更安静的噪音区。
真正把团队逼回慢链的,是一组没法再绕开的证据
19:26,现场终于出现了那个把争论压住的证据。
一位同学临时抽了 40 条慢请求的 traceId,按同一时间窗对了一遍,结果非常集中:
- 40 条里有 33 条,时间都耗在
order-service -> promotion-service -> coupon-rule-service这一段。 - 这些请求在
order-service本地执行业务逻辑只花了几十毫秒,并没有本地 CPU 忙或 GC 卡顿。 - 同一批请求到达
coupon-rule-service之后,数据库取券模板的 SQL p95 从 18ms 涨到 420ms。 - 而
coupon-template-cache命中率下滑,正好发生在 19:06,比网关 504 提前了 5 分钟。
这组证据有两个关键点。
第一,它把“慢”压到了同一段,而不是整个系统平均变慢。
第二,它把先后顺序钉住了:先是券模板缓存失去吸收,接着是规则服务回源变慢,再往外才是线程等待、重试放大、网关超时。
到这一步,团队才没法继续在“是不是先扩”“是不是先收告警”上打转,而是老老实实把那条慢链摊开。
那条链后来被还原成这样:
- 活动开始后,一批
coupon-templatekey 因为相同 TTL 集中过期。 promotion-service查询最优优惠时,大量回源到coupon-rule-service。coupon-rule-service读模板表,连接池 pending 持续升高。order-service同步等待优惠结果,业务线程被占住。- 同步重试把真实调用量从 1 倍放大到 1.6 倍。
- 最外层网关超时和 504 才开始集中暴露。
顺序就是在这时被事故逼出来的。
不是因为有人在会上讲明白了,而是因为前两种先手都没救场,只有这条链能同时解释:
- 为什么只有结算链先坏,别的读请求还没一起坏。
- 为什么扩了 Pod,慢请求还是慢。
- 为什么 504 最刺眼,却不是最早动的那一层。
慢链梳理之后,现场动作才第一次真正有效
链路一拉清,后面的动作一下子不再散。
19:29 开始,现场只做了三件和这条慢链直接相关的事:
- 关掉
confirmOrder这条链上的同步重试。 - 暂停券模板回填任务,避免和回源查询抢同一张表。
- 让最优优惠计算先走旧模板兜底,不再强制等待最新模板回源。
这三步下去之后,指标的回落顺序非常有说服力:
- 19:31,重试倍率先从 1.6 回到 1.1。
- 19:32,订单线程池 queue 开始下降。
- 19:33,
coupon-rule-service连接池 pending 明显回落。 - 19:35,结算链 p99 从 3s 降回 800ms 左右。
- 19:37,网关 504 才开始成片下降。
这个回落顺序又反过来验证了那条慢链没有看错。
如果起点真在网关、入口容量或者告警系统本身,这组指标不会按这个顺序往回掉。
它先掉的是放大器,再掉的是中间等待,最后才掉外层结果。
这比任何会议上的治理排序都更硬。
也是从那次之后,告警分层才真正重做成功
事故前,团队做过一次告警分层,但更多是按严重级别和责任团队分。
事故后,分法彻底换了。
不再先问 P1、P2,而是先问它在慢链里站哪一层。
后来结算链的主看板只保留三层信号。
第一层:起点层
专门看“第一张倒下的牌”有没有动:
coupon-template-cache命中率- 模板回源耗时
coupon-rule-service连接池 pending
这一层一旦连续异常,值班第一步不是拉群里所有人,而是先抽样 traceId,确认是不是这条老慢链又在起。
第二层:放大层
专门看系统有没有开始自我放大:
promotion-serviceclient timeoutconfirmOrder重试倍率order-service业务线程 queue
这一层抬起来,第一动作不是继续猜根因,而是先关重试、停回填、压低非核心计算,把放大器先拆掉。
第三层:结果层
最后才是用户和业务最先感知的结果:
- 结算超时率
- 网关 504
- 核心转化损失
这一层负责判断影响面和升级级别,不再承担“谁是起点”的职责。
也就是说,告警分层真正落地,不是因为颜色分得更细,而是因为慢链先被看清以后,每一层终于知道自己该服务什么动作。
容量评估也是在慢链梳理之后,才第一次算出了边界
事故前那份容量评估,主要看的还是平均 CPU、峰值 QPS、数据库利用率。
这些数不能说没用,但它们解释不了这次事故里最关键的事:
- 为什么缓存一失去吸收,系统很快就不是“慢一点”,而是整条链一起塌。
- 为什么重试倍率到 1.3 以后,扩 Pod 反而可能把回源压力继续往下送。
- 为什么连接数还有余量,用户请求还是已经超时。
事故后,团队重算容量时,不再先看全局平均值,而是只盯这条关键慢链的预算:
coupon-template-cache命中率不能长期掉到 92% 以下。coupon-rule-service连接池 pending 超过 150ms,就说明这条链已经接近失稳。confirmOrder同步重试倍率超过 1.25,就不能再继续放量。- 最优优惠计算必须有旧模板兜底,不允许把“等最新模板”当成默认路径。
后来重新压测时,团队第一次算出一个真正有意义的容量边界:
不是“结算服务 24 个 Pod 能扛多少 QPS”,而是“在模板缓存命中率、回源并发、重试倍率都受控的前提下,这条优惠慢链最多能承受多大流量;一旦哪个条件被打穿,应该先触发什么保护动作”。
这时容量评估才不再是离现场很远的一张报表,而是和真实故障链绑在一起的边界说明。
所以那次事故最后留下来的,不是一句治理口号,而是一条被验证过的顺序
后来再开稳定性治理会,大家不太再争“容量、慢链、告警,谁更基础”。
因为那次事故已经把顺序压得很清楚了。
如果连系统会沿哪条链变慢都说不清,先做容量,容易只算出平均余量;先做告警分层,也很容易只把结果层排得更漂亮。
只有先把那条会把现场拖垮的慢链找出来,后两件事才知道自己到底该落在哪里。
所以对那支团队来说,顺序不是在白板上排出来的,而是被事故硬生生逼出来的:
- 先把最危险的慢链梳清。
- 再把告警按这条链重做,让值班知道先看哪一层。
- 最后再按这条链去算容量、限流、重试和兜底边界。
这不是因为慢链这件事更高级。
而是因为没有它,后面两件正确的事都会先落空。
这篇的边界也得卡住
如果你的系统已经把关键慢链看得很清楚,告警也已经按链路摆好了,只差线程池、连接池、限流和超时预算,那就别被这篇拦住,直接做容量设计更合适。
如果你还在现场里,连最早分叉的是哪一层都没抓住,也别急着谈治理顺序,先回到具体故障链上。
这篇只想说明一件事:
稳定性治理里的先后顺序,很多时候不是靠抽象判断排出来的,而是靠一次事故里“哪种先手救不了场、哪条证据链能解释现场、哪组动作能被回落顺序验证”硬压出来的。