第2章 微服务架构中的雪崩效应

2.1 什么是雪崩效应

简单来说,雪崩就是一个小故障像多米诺骨牌一样,把整个系统拍在地上。

我记得刚接触微服务那会儿,觉得每个服务拆得挺干净,心里还挺美。结果线上一次密钥服务抖动,直接让全站接口全部报 503。那会儿我才真正体会到,什么叫牵一发而动全身。

微服务架构里,服务之间通过 RPC 或 HTTP 互相调用。一旦某个底层服务变慢或挂掉,上游服务会一直等它返回。等待的过程中,上游的线程池、连接池被慢慢占满。新请求进不来,就开始排队超时。超时又触发重试,重试再加重下游压力……恶性循环,直到整个链路崩溃。这就是雪崩。

核心特征:局部故障 → 资源耗尽 → 级联扩散 → 全局不可用

你想想看,密钥服务在鉴权链路里是刚需,几乎每个接口都要调它。一旦它出问题,影响面是几何级放大的。我见过一个项目,因为密钥服务响应时间从 5ms 涨到 500ms,上游 20 个服务开始排队超时,最终导致整个集群 OOM。嗯,这种事故谁都不想半夜被拉起来处理。

2.2 雪崩的典型成因

我把常见成因归了归类,拿密钥服务举例来说:

成因类别 具体表现 密钥服务场景
资源耗尽 线程池/连接池占满,新请求拒绝 密钥加解密操作太慢,占住所有 Worker 线程
超时堆积 调用方设置超时过长,请求排队积压 默认超时 60s,下游慢导致大量请求堆积在中游队列
重试放大 失败后立即重试,流量翻倍 客户端重试策略没配退避,每秒重试上千次
阻塞级联 单点故障导致上游全部阻塞 密钥服务依赖的 HSM 硬件卡死,所有调用方同步阻塞
缓存击穿 缓存失效瞬间,大量请求穿透到后端 密钥轮换时旧缓存过期,所有服务同时请求新密钥

我单独说说「重试放大」这个坑。曾经有个同事写重试逻辑时忘了加指数退避,结果密钥服务慢了一秒,客户端直接重试了 10 次,等于流量一下子放大到 10 倍。服务本来就扛不住,这下直接打挂。所以我在团队里定了个规矩:重试必须带退避,且总重试次数不超过 3 次。

2.3 雪崩的形成过程(流程图)

下面这张图可以直观地看到雪崩怎样一步步蔓延。从左到右看,正常时流量均匀,一旦下游出问题,上游积累,最终全局崩溃。

雪崩效应形成过程 正常流量 下游响应变慢 5ms→500ms 上游请求排队 线程池占满 级联崩溃 全局不可用 说明: ① 密钥服务变慢(如 HSM 拥塞) ② 调用方线程等待超时 → 线程池排队填满 ③ 新请求被拒绝或等待 → 调用方自己也开始堆积 ④ 级联传递至链路最前端 → 网关/入口全面超时

整个过程其实很快,从第一个请求超时到全站不可用,可能只需要几十秒。我亲眼见过 Grafana 面板上错误率从 0% 瞬间跳到 100%,连预警都来不及发。

2.4 雪崩的量化指标

为了及时发现雪崩前兆,我习惯关注这几个关键指标。一旦它们出现异常,就要立刻干预。

  • 错误率(Error Rate):5xx 比例持续超过 1%,需要警觉。
  • 响应时间(P99):密钥接口 P99 超过基线 3 倍,说明已经开始排队。
  • 线程池活跃数:接近最大线程数时,基本已经处于阻塞边缘。
  • 连接池使用率:达到 80% 以上,新连接可能要等待。
  • 重试次数:客户端重试次数突然增多,往往是被动触发。

我的实战建议:把密钥服务的线程池使用率和错误率做成聚合告警,两条同时触发时直接打 P0 电话。光一个指标波动可能是误报,两条一起动基本没跑。

2.5 一个真实案例的反思

几年前我负责一个支付平台的密钥服务。某次上线后,密钥缓存 TTL 设得太短,导致每分钟都有大量请求穿透到 HSM 硬件。硬件扛不住,开始超时。上游的订单服务、风控服务、通知服务都在同步等待,线程数猛涨。不到 3 分钟,整个集群 JVM 堆内存溢出了。

事后复盘,有三点教训:

  1. 没有设置调用超时:所有客户端都用的默认 60 秒超时,等于给雪崩留了充足的扩散时间。
  2. 没有限流机制:哪怕密钥服务自己扛不住,也该直接拒绝而不是傻傻排队。
  3. 没有熔断隔离:任何一个依赖方出问题,都会拖死整个调用链。

警告:千万不要以为密钥服务只在内网调用就不会崩。内网延迟再低,一旦流量爆发,或者硬件故障,效果跟外网是一样的。我曾经见过一个内网密钥服务因为磁盘 IO 打满,导致所有调用方阻塞,最后把整个微服务平台都拖挂了。

后来我们给所有依赖密钥服务的调用方加上了熔断器,配合超时和限流,再也没出过同级别的事故。下一个章节我会详细讲熔断降级的具体原理,这里先记住一件事:雪崩不是突如其来的,它有一连串的信号,关键是你有没有提前准备好手段去拦截它。