一般情况下,每个微服务之间是独立的,如果某个服务宕机,只会影响到当前服务,而不会对整个业务系统产生影响。但是,服务端可能会在多个微服务之间产生一条链式调用,并把整合后的信息返回给客户端。在调用过程中,如果某个服务宕机或者网络不稳定可能造成整个请求失败。因此,为了应对微服务的链式调用异常,我们需要在设计微服务调用链时不宜过长,以免客户端长时间等待,以及中间环节出现错误造成整个请求失败。此外,可以考虑使用消息队列进行业务解耦,并且使用缓存避免微服务的链式调用从而提高该接口的可用性。

“链不宜过长、MQ 解耦、缓存兜底”是三条设计原则,但生产系统还需要运行时防线:超时、重试、熔断、降级、隔离五件套,以及链路可用性的量化方法。本文把设计原则与运行时防线合成一套完整应对体系。

先量化:链式调用的可用性乘法

链路可用性 = 各环节可用性之乘积。四段调用各 99.9%,链路仅 99.6%(每月约 3 小时不可用);各 99%,链路 96%(每月约 29 小时)。链越长,可用性衰减越陡——这是”链不宜过长”的数学依据,也是评审调用链时的第一把尺子。

同理延迟是加法:四段各 P99=200ms,链路 P99 接近 800ms(实际因长尾叠加更差)。可用性与延迟双重衰减,决定了链式调用必须被治理而非放任生长。

设计期原则(原文三条的展开)

  • 缩短同步链:能异步的环节改 MQ(下单后发券、通知、积分异步化);能预计算的改缓存/物化视图(商品详情聚合页离线拼装)
  • 依赖分级:区分强依赖(缺失则请求失败)与弱依赖(缺失可降级);弱依赖一律”可降级设计”(默认值/缓存旧值/空集合)
  • 编排 vs 编排:长链改用编排式(orchestration,中心协调+状态机)或事件驱动(choreography),把”一条同步长链”拆为”多段短链+异步衔接”

运行时防线五件套

  1. 超时:每跳显式超时,且上游超时 > 下游超时之和(否则上游已放弃、下游还在算,浪费且重试放大);超时预算沿链传播(deadline propagation)
  2. 重试:仅幂等接口可重试;带退避+抖动;重试预算(重试流量占比上限,如 10%)防重试风暴;区分”连接失败可重试”与”超时结果未知慎重试”
  3. 熔断:下游错误率/慢调用率超阈值开路(快速失败),半开探测恢复——防止故障下游拖死上游线程池(Hystrix/Resilience4j/Sentinel)
  4. 降级:熔断后走兜底(缓存旧值、默认值、排队页、功能开关关闭非核心);降级预案要预先演练而非故障时现写
  5. 隔离:线程池/信号量按依赖隔离(bulkhead),慢依赖只耗尽自己的池,不波及主链路

五件套的组合顺序即一次调用的包裹层次:隔离 → 熔断 → 重试(内层)→ 超时(最内)→ 降级(熔断/重试失败后)。

失败语义设计:让失败”可理解”

  • 区分快速失败(下游明确 4xx/业务拒绝:不重试不熔断计数)与慢失败(超时/5xx:重试+熔断计数)
  • 链路级错误码透传(biz 前缀 + request_id,见追踪篇),客户端能区分”系统繁忙可重试”与”参数错误别重试”
  • 幂等键随重试透传,保证重试不重复执行(见幂等篇)

缓存与 MQ 的正确姿势(原文两条的边界)

  • 缓存兜底要设** stale-while-revalidate 语义**:下游故障时返回过期缓存并标记降级,恢复后回源刷新;无缓存可兜的强一致读只能降级为”稍后重试”
  • MQ 解耦引入最终一致:消费失败重试/死信(见消息队列篇),业务要接受”异步环节延迟可见”;对账任务兜底差异修复

演练与度量

  • 混沌工程/故障注入:定期杀下游、注入延迟,验证熔断降级真实生效(预案不演练等于没有)
  • 度量四指标:链路 P99 延迟、各环节错误率、熔断开路次数、降级命中率——降级命中率>0 说明防线在工作,长期为 0 要么没故障要么没生效

应对链式调用异常 = 设计期缩短与分级 + 运行时五件套 + 失败语义清晰 + 演练验证四层;可用性乘法是出发点,隔离与熔断是保命线,降级是用户体验的最后一米。