1. 链路为什么越写越长

我几年前接手过一个老项目,最离谱的一次链路是这样的:

网关 → A → B → C → D → E → F

六跳。每一跳都 80~120ms,最后 RT 是 600ms 起步。用户点个按钮,像是点到异地机房。

那时候我特别不理解:为啥一个查订单的接口,要查到 F 服务?后来才搞明白——大家做需求的时候都觉得“反正我这个接口需要那个数据,就直接调一下它嘛”,于是越积越厚,链路越拖越长。

问题的本质其实就一句话:大家都觉得自己的那一次调用没事,但所有调用叠在一起就出事了。

链路长,就像高速公路上加了好多收费站,这点过路费不是大问题,但排队排成长龙,一辆接一辆慢吞吞,就会把整条高速拖死。

2. 什么叫服务雪崩

简单说,就是一个服务挂了,导致十几个、几十个一起挂,然后网关直接 502 一片。

之前遇到过一次最典型的,当时我们的库存服务突然 RT 飙升,平时 20ms,那次变成了 2s;订单服务每个请求都要查库存,上游就跟着变慢;再往上用户下单就卡住;最后所有下单相关的业务全挂。

当时是晚上 10 点,业务方直接跑过来问:“怎么下个单能要这么久?”

我也只能苦笑:“不是我们想慢,是下游在那里深呼吸。”

3. 雪崩的根本原因

我后来把问题总结成三个字:等、堵、炸。

  • :超时设置太长,大家在那等结果,线程不释放,RT 越来越大
  • :大量调用挤在一起,下游撑不住
  • :线程池满了、连接池满了、队列爆了

有时候你看日志会发现:线程池 200 个线程,一个都没空,因为大家都在等待下游的 2s 超时。

这就是最可怕的:不是因为处理慢,而是因为一直在等,导致大家都没法工作。

很多团队(包括我们当年)都会犯同一个错:

默认超时值不改,库里多少就是多少。

然后就等着凌晨线上爆炸。

4. 超时时间到底怎么设

这件事我反复讲,讲到有点烦了,但真的太多人不重视。

我自己踩过一个坑:有一次一个接口因为访问第三方支付,超时时间设置成了 10 秒(对,就是我设的)。平时没感觉,但双十一那天因为对方偶尔卡一下,整个链路都跟着卡。

后来我总结了几条很简单但非常救命的原则:

超时设定原则
  • 超时必须小于自身 SLA 你自己要求接口 200ms 返回,你给下游设个 2s,是不是有点离谱?
  • 链路上每一跳都要递减 网关→A 最长 300ms A→B 200ms B→C 100ms
  • 所有超时必须按指标(99 分位)来算 不看平均值,平均值在高并发下毫无意义
  • 不要迷信下游 “我们这个服务很稳的,不会超时的” 这种话只适合写在墓志铭上。

5. 熔断器怎么工作的?Hystrix、Resilience4j 这些到底在干啥

很多人只知道“失败多了会熔断,但具体怎么判定的?”

我当年第一次用 Hystrix 时也只知道简单配置,直到某天线上被淹没,才研究得特别细。

核心逻辑其实就三个:

  1. 统计窗口(一般 10 秒)
  2. 阈值(失败请求达到一定比例就断)
  3. 半开探活(过一会儿放一部分流量试试是不是恢复了)

我第一次真正体会熔断的威力,是线上 C 服务突然 RT 飙到 5s,所有服务都在等它。

后来我们给 B→C 加了 Resilience4j,一旦失败率达到 50%,马上熔断,直接走降级逻辑。

结果非常明显:链路不再被拖垮,页面从 504 变成兜底数据,至少不至于挂死。

6. 隔离舱壁

讲人话就是:我不能因为你一个服务在拖延,把我的线程池全拖死。

Hystrix、Resilience4j 都有线程池隔离,重点就是——

每个下游调用自己的线程池,不许抢资源。

我们之前没有隔离时,库存服务 RT 2s 导致调用它的线程数占满了整个应用的线程池,其他接口完全没办法服务用户。

加了“舱壁”之后,库存服务爱慢不慢,慢死了也只是把自己的池子占满,不影响订单、商品详情那些正常流量。

这就是舱壁机制的本质。

7. 限流到底是令牌桶好还是漏桶好

我自己的体感是:

令牌桶更适合业务系统

因为能允许突发流量,比如突然来了 200 QPS,你不至于直接拒绝。

漏桶更适合对外接口

因为输出稳定,RT 可控。

当年我写过一个支付回调接口,用的是令牌桶,结果双十一凌晨,突发流量把桶打空了,直接拒绝了一堆回调。后来换成漏桶,流量下来了,但更稳。

对于内部系统,我一般建议使用令牌桶。因为微服务链路,不能太死板,总是要吃一点突发。

8. 降级策略

降级这事,大家都懂,但实际写出来的往往非常“羞羞的”。

比如我见过一个最离谱的降级:

代码语言:java

AI代码解释

catch (Exception e) {
    return null;
}

然后就造成了 NPE 满天飞。

我自己的做法:

  • 兜底返回(友好提示)
  • 缓存返回(比如 5 分钟内的旧数据)
  • 随机降级(比如 30% 的请求走兜底)

降级是让服务“趴着也能活”,不是让它“躺平”。

9. 链路追踪

以前没有 tracing 的时侯,排查问题就像摸黑找猫。加了 SkyWalking/Zipkin 后,真是一下清醒了。

有一次我们查一个慢接口,开发说“不可能是我这边,逻辑很简单的”。

链路追踪一打开:

A → B 100ms,B → C 1.4s

然后大家都沉默了。

追踪里看得最清楚的就是:

  • 哪个节点慢
  • 哪段网络延迟高
  • 是否有重试导致的抖动
  • 线程池是否排队

这东西平时没感觉,但线上爆炸的时候,是救命稻草。

10. 微服务架构里的 SLA 怎么定比较合理?

说实话,我以前以为 SLA 是业务方的事情,后来才明白:

没有清晰 SLA 的架构,是无法扩容、无法限流、无法保护的。

我的经验是:

  • 核心链路 SLA 200ms(比如下单、支付)
  • 查询类 SLA 500ms
  • 非核心异步可超过 1s

为什么要定 SLA?因为:

  • 限流要用 SLA
  • 超时要用 SLA
  • 要用 SLA

没有 SLA 的系统,就像没有体检报告的身体,看着好像还行,但随时可能倒下。

11. 如何避免级联失败

级联失败这个东西,戏剧性很强,轻则 504 一片,重则直接报警电话响一晚上。

我自己的经验:

  • 所有下游调用都要单独线程池
  • 每一跳都要有超时
  • 每一跳都要有熔断
  • 避免链路超过 3~4 跳
  • 强依赖必须预热(特别是缓存)

我当年踩得最痛的坑是:

我们有个服务启动后,缓存要预热 2 分钟。结果部署后还没预热好,就有请求进来,导致大量查询,DB 压力飙升,整个集群挂了。

那次之后我深刻意识到:

启动预热也是链路稳定性的一部分。

12. 一个真实案例

这是一个我印象特别深的线上事故。

某年 618 晚上 20:00,秒杀开始,我们系统平时 QPS 600,峰值能冲到 3000 左右,也不算很夸张。

结果就那一天库存服务突然 RT 变成了 800ms,我们完全没料到。原因是某个同事改了库存的查询逻辑,加了个“统计未支付订单数量”的 join,而且没走索引。

这 800ms 就像砸在水面上的石头:

  • 网关 RT 从 150ms 飙到 1500ms
  • A 服务线程池从 150 用满到 300
  • C 服务连接池爆掉,拒绝连接
  • 后面所有请求直接被 504

整条链路像一串多米诺骨牌,全倒了。

当时我看着监控,整整愣了十秒。

最后解决是这样:

  1. 在 A→库存 加了熔断
  2. 线程池隔离拉开
  3. 缓存兜底库存(允许 5 秒的旧数据)
  4. 第三分钟,库存服务恢复正常
  5. 全链路才慢慢拉回来

更多推荐