你家配电箱里有一枚空气开关。平时它默默无语,电流正常输送;一旦电路短路或过载,它“啪”地跳闸,切断整屋电源。你重新推上闸,如果一切正常,生活恢复;如果又跳,说明故障还在。这不是一句“小心用电”的提醒,而是一套精密的保护机制。 高并发系统里也藏着太多这样的“短路时刻”:一个下游服务变慢,请求排队堆积,线程池被耗尽,故障像多米诺骨牌一样逐级放大,最终整条调用链雪崩。熔断器(Circuit Breaker)就是架构师装在服务链路里的空气开关——它监测每一次调用的成败,在故障失控前主动切断请求,给下游留出恢复时间,用“关闭、打开、半开”三态守护整个系统的可用性。 这篇文章将从家庭电路出发,讲透熔断器的三态转换、故障级联、滑动窗口统计、降级策略,并对比主流的 Hystrix 与 Resilience4j 实现。读完你不仅知道熔断器“是什么”,更能理解它“为什么能”保护系统,以及在生产中如何配置和取舍。

岗位关联

熔断器是每位后端与 AI 架构师的必修课:在模型服务、特征链路、微服务调用中,它帮你把故障隔离在局部,让可用性从“单点努力”变成“系统韧性”。

目录
  1. 可视化演示
  2. 熔断三态
  3. 故障级联
  4. 状态转换
  5. 滑动窗口
  6. 降级策略
  7. 实现对比
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示熔断器Closed/Open/Half-Open三态转换、滑动窗口统计与Hystrix vs Resilience4j对比。

本节配有交互动效(进入视口后自动加载,约 3KB)。

熔断器三态转换 Closed:正常放行请求 Open:熔断拒绝所有请求 CLS→(ERR>50%)→OPN→(TMO)→HLF→(OK)→CLS 半开态探测恢复后回到关闭态

熔断三态

想象你家配电箱里的空气开关。平时闸门合上,电流正常输送到每个房间——这是“关闭态”。某天厨房电路短路,电流瞬间飙高,空气开关“啪”地一声跳开,整屋断电——这是“打开态”。夜里你重新推闸,心里忐忑:如果推上去不再跳闸就恢复正常,如果又跳说明短路还在——这个“试推一下”的瞬间,就是“半开态”。

分布式系统中的熔断器(Circuit Breaker)就是从这枚空气开关身上借来的智慧。它守护在服务调用链的关键节点上,同样维护三个状态:CLOSED(关闭)、OPEN(打开)、HALF\_OPEN(半开)。关闭态时请求正常放行,打开态时所有请求直接快速失败,半开态时放行少量试探请求观察下游是否恢复。

三个状态不是静止的,而是随着请求结果动态流转。当调用失败的次数达到阈值(例如连续失败5次),熔断器从 CLOSED 进入 OPEN;在 OPEN 态等待一个冷却时间(如5秒)后自动进入 HALF\_OPEN;在 HALF\_OPEN 态,如果试探请求成功,说明下游已恢复,熔断器回到 CLOSED;如果失败,则重新进入 OPEN 并再次计时。

就像空气开关“记”着最近是不是总跳闸,熔断器也在心里默默计数。跳闸不是目的,保护才是——家里跳闸后你会减少同时开空调、烤箱和电磁炉;系统熔断后,下游服务也获得了喘息和恢复的机会。

Fc≥N  ⇒  CLOSED→OPEN\displaystyle F_c \ge N \;\Rightarrow\; \text{CLOSED} \to \text{OPEN}
连续失败次数 \(F_c\) 达到阈值 \(N\) 时,熔断器从关闭状态切换到打开状态。
  1. 失败计数\(F_c\) 表示连续失败次数,每次调用失败累加一次,成功则清零。
    Fc=Fc+1(调用失败)F_c = F_c + 1 \quad (\text{调用失败})
  2. 阈值判断\(N\) 是预先配置的失败阈值,当 \(F_c\) 达到 \(N\) 时条件成立。
    Fc≥NF_c \ge N
  3. 状态迁移\(\Rightarrow\) 表示条件满足后触发状态迁移,CLOSED 变为 OPEN。
    CLOSED→OPEN\text{CLOSED} \to \text{OPEN}

故障级联

如果厨房短路时空气开关没有跳闸会怎样?电流沿着电线发热、冒烟,点燃可燃物,火势沿着线槽蔓延到客厅、卧室,最终整栋楼陷入火海。一个插座的问题,变成了整栋楼的灾难——这就是“故障级联”。

在微服务架构里,这个“火灾”过程每天都在酝酿:服务A调用服务B,B调用C。当C变慢或宕机,B发出的大量请求就会阻塞在等待中,B的线程池被耗尽,B自己也“瘫痪”了。接着A发往B的请求也全部超时,A的资源也被拖垮。故障像多米诺骨牌一样,从C一路推到A,甚至波及整条调用链——业内称之为“雪崩效应”。

为什么会这么严重?因为每个服务的线程和内存都是有限的。链路中每一个环节的故障都会放大上游的等待成本。用一个公式来表达链路整体可用性的衰减:假设链路上有 \(n\) 个服务相互串联,每个服务的可用性为 \(A_i\),那么整条链路的可用性是它们的乘积。这不仅是数学直觉,更解释了为什么“高可用”不能只靠单点保障——任何一个薄弱环节都会拉低全局。

就像消防系统里那道关键防火门——熔断器的作用就是在“火势”蔓延到上一层之前,强行切断一条链路,把损失控制在局部,而不是让故障像野火一样烧遍整栋楼。

Atotal=∏i=1nAi\displaystyle A_{\text{total}} = \prod_{i=1}^{n} A_i
整条调用链的可用性等于每个环节可用性的乘积。假设每个服务可用性为99%,4个服务串联后整体可用性约为96%。
  1. 单节点可用性\(A_i\) 指链路上第 \(i\) 个服务的可用性,比如99%记为0.99。
    Ai=成功请求数总请求数A_i = \frac{\text{成功请求数}}{\text{总请求数}}
  2. 连乘符号\(\prod_{i=1}^{n}\) 表示从第1个服务到第 \(n\) 个服务依次相乘。
    ∏i=1nAi=A1×A2×⋯×An\prod_{i=1}^{n} A_i = A_1 \times A_2 \times \cdots \times A_n
  3. 整体可用性\(A_{\text{total}}\) 是链路最终可用性,服务越多、乘积越小。
    Atotal=0.99nA_{\text{total}} = 0.99^n

状态转换

空气开关为什么能区分“偶尔的电压波动”和“真正的短路”?因为它内置了一套判断逻辑:在极短的时间窗内,如果电流异常出现的频率超过某个标准,才会触发跳闸。这不是拍脑袋,而是精密的机电设计。熔断器的状态转换,同样依赖一套明确的判定逻辑。

在工程实现中,熔断器不能因为一两次失败就大惊小怪地打开。它需要在一个统计窗口内观察:请求总数是否达到最低样本量,失败率是否超过阈值。只有当两个条件同时满足时,才认为下游确实发生了持续性故障,触发从 CLOSED 到 OPEN 的迁移。

Resilience4j 的判定条件可以形式化地写成:窗口内总请求数 \(R_w\) 大于等于最低调用数 \(N_{\min}\),并且窗口内失败率 \(F_w / R_w\) 大于等于失败率阈值 \(T_{\text{fail}}\)。这个双重条件保证了判断的可靠性:样本太少不判断,波动不大不跳闸。

这套逻辑就像空气开关里的双金属片——不是一热就跳,而是“足够热”而且“持续足够久”才跳。熔断器的“理性”,正是它比手工重启靠谱一万倍的地方。

STATE=OPENifRw≥Nmin⁡  ∧  FwRw≥Tfail\displaystyle \text{STATE} = \text{OPEN} \quad \text{if} \quad R_w \ge N_{\min} \;\wedge\; \frac{F_w}{R_w} \ge T_{\text{fail}}
只有当窗口内请求量足够大、且失败率足够高时,熔断器才从关闭切换为打开,避免误判。
  1. 窗口请求总数\(R_w\) 表示当前滑动窗口内所有请求的总数,包含成功与失败。
    Rw=Rsuccess+FwR_w = R_{\text{success}} + F_w
  2. 窗口失败率\(\frac{F_w}{R_w}\) 用失败数除以总请求数得到失败率。
    FailureRate=FwRw\text{FailureRate} = \frac{F_w}{R_w}
  3. 与逻辑\(\wedge\) 表示两个条件必须同时成立,缺一不可。
    (Rw≥Nmin⁡)∧(FailureRate≥Tfail)(R_w \ge N_{\min}) \wedge (\text{FailureRate} \ge T_{\text{fail}})

滑动窗口

如果保护闸只统计“这台电表自从装好以来总共出现过多少次异常”,那老房子的保护闸恐怕永远都不会跳——因为过去十年它一直正常,今天冒烟也拉低不了平均值。所以,真正靠得住的保护闸关心的是“此时此刻最近的状况”,而不是“这辈子平均的状况”。

熔断器同样不统计“自启动以来”的累计失败率,因为这会让历史正常数据掩盖当下故障。它采用的是滑动窗口(Sliding Window)机制:只关心最近一段时间(例如最近10秒或最近100次请求)内发生的成功与失败。超过窗口的数据被“滑出”,不再参与统计。这样,当故障在某一刻突然爆发时,失败率能迅速反映出来。

滑动窗口最常见的实现是把时间切成一个个小桶(bucket),每个桶记录该秒内的请求数。窗口向前滚动时,最老的桶被丢弃,最新的桶被加入。窗口内失败率的计算公式如下:分子是窗口内失败请求总数,分母是窗口内总请求数,两者都是对桶内计数的求和。

这就像你手机上那个“最近7天步数”统计:每天结束,最远的那天滑出去,今天加进来。滑动窗口让熔断器始终把目光聚焦在“当下”,而不是被遥远的过去蒙蔽了双眼。

FailureRate(t)=∑i=t−w+1tFi∑i=t−w+1tRi\displaystyle \text{FailureRate}(t) = \frac{\sum_{i=t-w+1}^{t} F_i}{\sum_{i=t-w+1}^{t} R_i}
在时刻 \(t\),只统计最近 \(w\) 个时间粒度内的失败数与请求总数,二者之比即为窗口失败率。
  1. 窗口起点\(t-w+1\) 表示窗口的起始时间,\(w\) 是窗口宽度,\(t\) 是当前时刻。
    t−w+1≤i≤tt-w+1 \le i \le t
  2. 失败数求和\(\sum_{i=t-w+1}^{t} F_i\) 将窗口内每个时间粒度记录的失败请求数累加。
    SumFail=Ft−w+1+⋯+Ft\text{SumFail} = F_{t-w+1} + \cdots + F_t
  3. 总请求数求和\(\sum_{i=t-w+1}^{t} R_i\) 将窗口内所有请求数累加。
    SumReq=Rt−w+1+⋯+Rt\text{SumReq} = R_{t-w+1} + \cdots + R_t
  4. 失败率两者相除得到窗口失败率,用于与阈值比较。
    FailureRate=SumFailSumReq\text{FailureRate} = \frac{\text{SumFail}}{\text{SumReq}}

降级策略

停电了怎么办?你会摸出手电筒、点亮蜡烛,该有的安全感不能丢。虽然大功率电器没法用了,但基本照明还能维持——这就是生活中的“降级策略”。

熔断器打开之后,请求不能永久悬空,也不能让用户傻傻等待超时。此时需要快速返回一个降级结果(fallback):也许是默认推荐列表、缓存中的旧数据、或者一个友好的错误提示。降级让服务在“亚健康”状态下依然能提供有限但可用的能力,而不是彻底失效。

降级的开销远低于真正调用下游。设检测时间为 \(T_{\text{detect}}\),fallback 执行时间为 \(T_{\text{fallback}}\),则降级后的总响应时间 \(T_{\text{degraded}}\) 近似为两者之和。因为 \(T_{\text{detect}}\) 通常只有几毫秒,所以即便 fallback 逻辑稍重,用户也不会明显感知到延迟。常见的降级手段包括:返回静态默认值、读取本地缓存、调用备用服务、直接抛异常由上层统一处理。

就像手电筒虽然比不上电网照明,但足够你在黑暗中看清路。降级策略的目标不是“和原来一样好”,而是“在故障时依然可用”——在可用性面前,完美让位于务实。

Tdegraded=Tdetect+Tfallback\displaystyle T_{\text{degraded}} = T_{\text{detect}} + T_{\text{fallback}}
降级后的总耗时等于熔断器发现故障并切断请求的时间,加上执行兜底逻辑的时间。
  1. 检测时间\(T_{\text{detect}}\) 指熔断器发现故障并快速失败所需的时间,通常在毫秒级。
    Tdetect≪TtimeoutT_{\text{detect}} \ll T_{\text{timeout}}
  2. 降级执行时间\(T_{\text{fallback}}\) 指执行 fallback 逻辑的时间,比如读一次本地缓存。
    Tfallback≈1∼10msT_{\text{fallback}} \approx 1 \sim 10 \text{ms}
  3. 降级总耗时\(T_{\text{degraded}}\) 是用户感知到的总响应时间,远小于超时时间。
    Tdegraded=Tdetect+Tfallback≪TtimeoutT_{\text{degraded}} = T_{\text{detect}} + T_{\text{fallback}} \ll T_{\text{timeout}}

实现对比

市面上有老式保险丝也有现代空气开关。老式保险丝烧断后必须换新的(一次性),现代空气开关跳闸后推回去就能复用(可重置)。Hystrix 和 Resilience4j 的关系,有点像“老式保险丝”和“现代空气开关”:理念相同,细节和体验截然不同。

Hystrix 是 Netflix 在 2012 年开源的熔断及隔离框架,一度是 Java 微服务容错的事实标准;Resilience4j 是一个更轻量、函数式风格的容错库,也是 Hystrix 停更后社区推荐的首选继任者。两者都实现了熔断器模式,但在统计机制、隔离方式和配置模型上存在显著差异。

Hystrix 使用线程池隔离:每个依赖单独占有一个线程池,线程耗尽后快速失败。线程池大小需要根据 QPS 和延迟来估算。Resilience4j 则更轻量,它不强制使用线程池,默认基于信号量在调用线程内进行熔断判断;同时它用真正的滑动窗口(基于计数的环状桶)来统计失败率,比 Hystrix 的滚动窗口更精细。Hystrix 的线程池隔离更彻底、能切断线程,但线程切换开销大;Resilience4j 的信号量隔离更快、资源占用更低,但不能中断正在执行的慢请求。

选保护闸时,老电工喜欢老式保险丝,觉得“烧断就是烧断,清清楚楚”;年轻人喜欢空气开关,“跳了推回去就行,还能自动尝试恢复”。选 Hystrix 还是 Resilience4j,本质上是选择一种与自身架构匹配的容错哲学。

corePoolSize=QPSpeak×P99×(1+冗余系数)\displaystyle \text{corePoolSize} = \text{QPS}_{\text{peak}} \times P_{99} \times (1 + \text{冗余系数})
估算 Hystrix 线程池大小时,用峰值 QPS 乘以 P99 延迟(秒),再预留一定冗余,得到所需的并发线程数。
  1. 峰值吞吐\(\text{QPS}_{\text{peak}}\) 是下游依赖在高峰期每秒被调用的次数。
    QPSpeak=max⁡tQPS(t)\text{QPS}_{\text{peak}} = \max_{t} \text{QPS}(t)
  2. 延迟上界\(P_{99}\) 表示99%的请求在多少秒内完成,估算每个请求占用的线程时长。
    P99=percentile(latency,0.99)P_{99} = \text{percentile}(\text{latency}, 0.99)
  3. 冗余缓冲\((1 + \text{冗余系数})\) 给线程池预留额外缓冲空间,应对突发流量。
    corePoolSize=QPS峰值×平均延迟×1.2\text{corePoolSize} = \text{QPS}_{\text{峰值}} \times \text{平均延迟} \times 1.2

AI 应用场景

AI推理服务调用链的熔断保护

阶段:模型服务部署与运维(模型上线后的高可用保障)

解决的问题:AI推理服务通常依赖特征服务、向量检索等下游组件。当这些组件发生故障或性能劣化时,如果没有熔断,推理服务的线程池会被慢调用占满,GPU 资源浪费在等待上,用户体验急剧恶化。传统做法是无限扩大超时时间和线程池,但这只是把问题往后推。

具体案例:某推荐团队的实时特征服务依赖 Redis 集群,大促期间 Redis 主从切换引发读延迟从 1ms 飙升到 800ms。接入熔断器后,失败率超过 50% 时熔断器打开,特征服务在 10ms 内返回默认 embedding,推荐接口成功率保持在 99.9%,大促平稳度过。

大模型API网关的降级与熔断

阶段:LLM 应用网关层(模型服务的流量入口)

解决的问题:企业 AI 助手接入 GPT-4 等大模型 API,上游限流或故障时,如果让用户傻等 30 秒超时,体验极差。熔断器在网关层快速失败并提供降级回复,让用户始终得到响应。

具体案例:某客服机器人调用大模型接口,当上游连续 5 次返回 429/5xx 时熔断打开,网关直接返回“AI 助手正在休息,请稍后再试”,同时把请求转到基于缓存知识库的意图匹配模型,保证用户始终有响应而不必干等。

Python 演示

python105 行
# -*- coding: utf-8 -*-"""简易熔断器:三态转换 + 滑动窗口失败率统计"""import timefrom collections import deque  class SlidingWindow:    """滑动窗口:记录最近 max_len 次调用的失败情况"""     def __init__(self, max_len=20):        self.max_len = max_len        self.records = deque(maxlen=max_len)     def add(self, is_failure):        self.records.append(1 if is_failure else 0)     def failure_rate(self):        if not self.records:            return 0.0        return sum(self.records) / len(self.records)  class CircuitBreaker:    """熔断器:关闭 -> 打开 -> 半开 -> 关闭"""     def __init__(self, fail_rate_threshold=0.5,                 min_calls=5, cooldown=1.0, half_open_max=2):        self.window = SlidingWindow(min_calls)        self.fail_rate_threshold = fail_rate_threshold        self.min_calls = min_calls        self.cooldown = cooldown        self.half_open_max = half_open_max        self.state = 'CLOSED'        self.open_since = 0.0        self.half_open_count = 0     def allow_request(self):        """判断当前请求是否放行(非阻塞)"""        if self.state == 'CLOSED':            return True        if self.state == 'OPEN':            if time.time() - self.open_since >= self.cooldown:                self._to_half_open()                return True            return False        if self.half_open_count < self.half_open_max:            self.half_open_count += 1            return True        return False     def record(self, success):        """记录一次调用结果并更新状态"""        self.window.add(not success)        if self.state == 'HALF_OPEN':            if success:                self._to_closed()            else:                self._to_open()            return        if self.state == 'CLOSED' and \                len(self.window.records) >= self.min_calls and \                self.window.failure_rate() >= self.fail_rate_threshold:            self._to_open()     def _to_open(self):        self.state = 'OPEN'        self.open_since = time.time()     def _to_half_open(self):        self.state = 'HALF_OPEN'        self.half_open_count = 0     def _to_closed(self):        self.state = 'CLOSED'        self.window.records.clear()  def main():    breaker = CircuitBreaker(fail_rate_threshold=0.4,                             min_calls=5, cooldown=1.0, half_open_max=3)    calls = 0     def unstable_api():        nonlocal calls        calls += 1        if calls <= 5:            raise RuntimeError('下游服务异常')        return 'ok'     for i in range(20):        if not breaker.allow_request():            print(f'[{i:2d}] 被熔断,降级返回缓存数据')            continue        try:            result = unstable_api()            breaker.record(True)            print(f'[{i:2d}] 调用成功: {result}')        except RuntimeError as e:            breaker.record(False)            print(f'[{i:2d}] 调用失败: {e}')        time.sleep(0.2)  if __name__ == '__main__':    main()
目的:用约 100 行 Python 实现一个带滑动窗口统计的熔断器,模拟不稳定服务并演示“关闭→打开→半开→关闭”的完整状态流转。
  1. self.window.add(not success)把本次调用结果写入滑动窗口,失败记1、成功记0。
  2. self.window.failure_rate() >= self.fail_rate_threshold当窗口内失败率达到阈值时,驱动状态转换。
  3. if not breaker.allow_request():熔断打开时请求被快速拒绝,不会真正打到下游服务。
  4. if time.time() - self.open_since >= self.cooldown:冷却时间结束后,打开态自动转入半开态。
  5. def unstable_api():模拟一个“前5次失败、之后恢复”的真实服务,验证熔断器的自动恢复能力。

常见误区

误区我一直以为熔断器就是给每个请求设一个超时时间,只要请求超时就报错返回,这样就能防止服务被拖垮。

正解超时保护的是“单个请求”的等待上限,而熔断保护的是“整体链路的可用性”。熔断器统计最近一段时间的失败率,达到阈值后从源头切断所有流量,让下游服务彻底“断电休息”。用空气开关打比方:超时是电饭煲自己的断电保护,熔断是整屋跳闸。

为何常见:超时、重试、熔断、限流都属于容错手段,初学者容易把它们混淆成同一种机制,忽略了它们作用在不同层面。

误区我以为熔断器打开后就要一直拒绝请求,直到运维人员手动重启或恢复才能重新放行。

正解熔断器会自动进入半开状态,放行试探请求。如果试探成功,就自动恢复为关闭态;如果失败,重新打开并重新计时。整个过程自动完成,无需人工干预。这就像空气开关跳闸后可以自动推回,而不是像保险丝一样烧断就必须换新的。

为何常见:因为对“打开态”缺乏后续机制的理解,误以为熔断器是一次性的,忽略了它内置的自动恢复设计。

误区半开状态就是恢复正常放行,所有请求都可以通过,无非是名字叫“半开”而已。

正解半开状态只放行少量试探请求(如3-5个),用来探测下游是否恢复。放行太少则判断不准确,放行太多则起不到保护作用。这是熔断器设计中“试探”与“保护”的折中。从字面理解“半开”为“开了一半”是错误的,它的精髓是“小流量验证”。

为何常见:从字面含义去猜测术语,没有理解半开状态在状态机中的定位是“谨慎验证”而非“部分放行”。

误区熔断器统计的是服务自启动以来的总体失败率,只要总体失败率不高,就不用打开熔断器。

正解必须用滑动窗口统计“最近一段时间”的失败情况。如果一个服务前两天稳定、今天突然故障,累计失败率仍然很低,会延误熔断触发。滑动窗口保证系统能及时感知近期故障,而不是被长期平均数据掩盖。就像手机步数统计只看最近7天而不是出生以来的总步数。

为何常见:直观上认为“长周期平均”能代表系统健康度,没有意识到故障的突发性和时效性才是熔断器需要捕捉的信号。

面试题

Q1. 熔断器(Circuit Breaker)维护的三个核心状态是什么?

单选题 · easy

  • A.开启、关闭、重启
  • B.关闭、打开、半开← 正确答案
  • C.运行、停止、暂停
  • D.启动、初始化、就绪

答案:B。熔断器的三态是 CLOSED(关闭)、OPEN(打开)、HALF\_OPEN(半开),对应家庭空气开关的合上、跳开、试探推闸三种情形。其他选项都是进程生命周期的概念,不是熔断器的状态。

Q2. 熔断器处于打开状态时,请求会如何处理?

单选题 · mid

  • A.正常转发到下游服务处理
  • B.直接快速失败并返回降级结果← 正确答案
  • C.将请求放入队列等待重试
  • D.暂停所有请求直到超时

答案:B。打开状态意味着“断电保护”,所有请求直接快速失败并返回 fallback 结果,不再消耗下游资源,也不会让调用方线程继续等待。这就像空气开关跳闸后,电流被切断而不是继续试探电路是否还短路。

Q3. 熔断器进入半开状态的主要目的是什么?

单选题 · mid

  • A.放行所有请求对服务进行压测
  • B.放行少量请求试探服务是否恢复← 正确答案
  • C.拒绝所有请求并直接返回降级
  • D.暂停所有状态统计和计数工作

答案:B。半开状态的核心是“试探”:放行少量请求验证下游是否已恢复。如果试探成功则回到关闭态,失败则重新打开。这就像推上空气开关后等待几秒,看它是否会再次跳闸。

Q4. 触发熔断器从关闭态进入打开态通常需要哪些条件同时满足?

多选题 · mid

  • A.窗口内失败率超过预设阈值← 正确答案
  • B.窗口内总请求数达到最低统计量← 正确答案
  • C.下游服务的CPU使用率超过九成
  • D.某个请求的响应时间出现超时

答案:AB。熔断器要求“样本量足够”且“失败率足够高”才触发,避免偶发波动造成误判。C 是下游自己的监控指标,D 是单个请求的问题,都不属于熔断器的统计条件。

Q5. Hystrix 与 Resilience4j 相比,最核心的差异是什么?

单选题 · mid

  • A.Hystrix 是同步框架而 Resilience4j 是异步框架
  • B.Resilience4j 轻量函数式而 Hystrix 继承式← 正确答案
  • C.Hystrix 不支持任何形式的服务降级
  • D.Resilience4j 不支持分布式链路追踪

答案:B。Hystrix 要求继承 HystrixCommand 类并以线程池隔离为主;Resilience4j 以函数式 API 提供熔断、限流、隔离等能力,基于信号量和滑动窗口,更轻量。Hystrix 官方已停更,Resilience4j 是主流继任者。

Q6. 滑动窗口统计相比全局累计计数,最突出的优势是什么?

单选题 · hard

  • A.统计计算速度更快效率更高
  • B.反映近期失败率避免历史拖累← 正确答案
  • C.不需要额外存储历史请求数据
  • D.可以用简单计数变量直接实现

答案:B。全局累计计数会被长期正常数据稀释,故障爆发时无法及时反映。滑动窗口只统计最近 N 个请求或最近 T 秒内的数据,能让熔断器快速感知近期故障,像看“最近7天步数”而不是“出生以来总步数”。

Q7. 熔断器打开后,常用的降级策略包括哪些?

多选题 · mid

  • A.返回默认值或预设的兜底结果← 正确答案
  • B.从缓存中读取上一次的成功数据← 正确答案
  • C.将请求用XML格式重新序列化
  • D.直接关闭服务并拒绝所有流量

答案:AB。降级的本质是“有回应比没回应好”:返回默认值、读缓存、调用备用服务都是常见做法。C 是数据格式转换,与降级无关;D 是彻底不可用,违背了降级“保持基本可用”的初衷。

Q8. 在 AI 推理服务调用链中接入熔断器,最主要的作用是什么?

单选题 · mid

  • A.提高模型单次推理的准确率
  • B.保护上游资源不被慢调用拖垮← 正确答案
  • C.加速大模型的训练收敛过程
  • D.减少推理服务的内存占用比例

答案:B。AI 服务经常依赖特征、向量检索等下游组件,当下游变慢时,推理线程会被大量占用,GPU 在等待中浪费。熔断器快速切断故障链路,让推理服务把资源留给正常请求,这是高可用层面的保障,与模型效果无关。

Q9. 假设一条调用链上有 n 个服务,各服务可用性均为 0.99,整条链路的可用性如何计算?

单选题 · hard

  • A.整体可用性等于各服务可用性之和
  • B.整体可用性等于各服务可用性连乘← 正确答案
  • C.整体可用性等于各服务可用性取最小
  • D.整体可用性等于各服务可用性取最大

答案:B。串联链路的可用性按乘法计算:0.99 的 n 次方。服务越多乘积越小,所以任何一个薄弱环节都会拉低全局,这也是必须用熔断器切断单点故障的原因。

Q10. 关于熔断器的半开状态,以下说法正确的是哪些?

多选题 · hard

  • A.半开放行的试探请求数量由配置决定← 正确答案
  • B.试探请求成功熔断器随即恢复为关闭态← 正确答案
  • C.试探请求失败熔断器重新回到打开态← 正确答案
  • D.半开状态将一直持续直到服务恢复

答案:ABC。半开状态放行的请求数由 halfOpenMax 等参数控制;试探成功说明下游已恢复,回到关闭态;试探失败说明故障仍在,重新打开并重新计时。半开不会无限持续,而是由试探结果驱动快速转换,D 错误。