想象你是城里最火的粤菜馆的后厨总管。你的后厨有十几个“外援”:海鲜供应商、蔬菜供应商、卤味师傅——每道菜都要向他们“下订单”,就像微服务之间互相调用接口。突然有一天,海鲜供应商的冷库坏了,你连续打电话催货,对方一直占线。这时候,出菜口已经堆了三十桌客人的单子,全部卡在“等海鲜”这一步。更糟的是,等得越久,客人越不满意,整个后厨的节奏全被这一条线拖垮。这一眼就能看懂的厨房危机,正是微服务世界里每天都在上演的“雪崩”。 微服务系统里,服务 A 调用服务 B,B 又调用 C。任何一个下游服务变慢或宕机,排队的请求就会像堆积的订单一样,逐渐耗尽调用方的线程和内存,最终整条链路崩盘。聪明的系统设计者发明了一个像家里保险丝一样的装置——熔断器(Circuit Breaker)。它时刻盯着下游的健康状况,一旦发现失败率超标,就果断“拉闸”,不再傻等,而是立刻返回兜底结果,给下游留出喘息时间。这就是微服务熔断降级的核心思想。

岗位关联

对后端与 AI 工程师而言,熔断降级是守住系统可用性的生命线。掌握它,你就能在大流量冲击下从容止损,这也是高可用架构面试的高频考点。

目录
  1. 可视化演示
  2. 熔断器原理:电路里的“总闸”
  3. 状态流转:拉闸、试探、恢复的三角舞
  4. 滑动窗口:只看最近的账本
  5. 降级策略:换上备选菜单
  6. 限流配合:门口先放号
  7. 舱壁隔离:船上的水密隔舱
  8. 实战配置:把参数调成手艺
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示熔断器三态转换机制、降级策略执行流程与滑动窗口失败率统计算法。

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

熔断器三态转换 关闭态正常放行请求失败率超阈值转开启态 开启态拒绝请求直接返回降级响应 R=f/n 失败率等于失败数除以总请求数

熔断器原理:电路里的“总闸”

你家的电闸盒里躺着一根不起眼的保险丝。某个深夜,你同时开了空调、烤箱、电暖器,电流瞬间超过线路承受上限——保险丝“啪”一声熔断,全屋断电,但线路和电器保住了。没有这根保险丝,过载会让电线发热起火,烧掉整栋房子。微服务里的熔断器,就是同一根保险丝:它不能阻止故障发生,但能在故障扩大前果断切断,保住更大的系统。

把视线拉回那家粤菜馆:海鲜供应商出问题后,热菜档口每出一道“清蒸石斑”都要干等供应商回复。十分钟后,等待的订单占满了出菜台,连其他畅销菜也做不了。服务 A 调用服务 B 时,B 变慢或宕机,A 里的请求同样不会自动放弃,而是一个个排队等待响应,逐渐占满线程和内存,A 自己也被拖死;A 挂了,调用 A 的上游又开始堆积请求——这就是多米诺骨牌式的“雪崩效应”。

熔断器的应对思路拆成三步:统计、判断、快速失败。它在滑动窗口内记录每次调用的成败,一旦失败率超过阈值,立刻“拉闸”:后续请求不再真正打到下游,而是瞬间返回错误或兜底值,让调用方立刻拿回线程。这个“立刻”切断了下游故障向上游传导的链条,核心依据就是失败率:

保险丝熔断后,你不会立刻合闸,而是先排查是哪个电器过载。熔断器也一样,拉闸只是第一步——接下来如何恢复、何时恢复,就是状态流转的智慧了。

Rfail=FNR_{\text{fail}} = \frac{F}{N}
失败率 R 等于窗口内失败次数 F 除以总请求数 N。熔断器拿它和阈值比较,决定是否拉闸。
  1. 失败次数 F窗口内被标记为失败(超时、异常、错误码)的调用总数,是熔断的导火索。
    F=∑i=1kfiF = \sum_{i=1}^{k} f_i
  2. 总请求数 N窗口内所有发往下游的调用数,成功失败都算,避免小样本误判。
    N=∑i=1kniN = \sum_{i=1}^{k} n_i
  3. 失败率 RF 除以 N,当它超过阈值 θ(如 0.5),熔断器从闭合翻转为打开。
    Rfail=FN>θR_{\text{fail}} = \frac{F}{N} > \theta

状态流转:拉闸、试探、恢复的三角舞

供应商出事后,后厨主管做的第一件事是“拉闸”:暂停向该供应商下单,所有海鲜类菜品临时下架。过了一段时间,维修队说冷库修好了,主管不会一口气下五十单,而是先试着下一单——对方接住了,才逐步恢复供应。这个“拉闸—试探—恢复”的过程,正是熔断器的状态流转。

熔断器有三个状态:闭合(CLOSED)、打开(OPEN)、半开(HALF_OPEN)。闭合时一切正常,请求像平时一样通过;一旦失败率超过阈值,熔断器跳到打开状态,所有请求被快速拒绝,连发往下游的机会都没有;等了一个“睡眠期”,熔断器进入半开状态,放行少量试探请求——成功了说明下游缓过来了,转回闭合;失败了则弹回打开,再等一轮。

这里有个关键问题:打开状态要等多久?等太短,下游还没恢复就又被打垮;等太长,白白损失可用性。聪明的做法是指数退避——每次熔断的等待时间随重试次数翻倍,让故障服务有越来越长的恢复窗口:

半开试探就像餐厅先下一单试供应:只放少量请求、谨慎验证。跳闸后的等待节奏,决定了系统是高傲地错过恢复时机,还是鲁莽地反复踩雷。

Twait=min⁡(Tmax,  Tbase×2n)T_{\text{wait}} = \min \left( T_{\text{max}},\; T_{\text{base}} \times 2^{n} \right)
等待时间在“基础等待 × 2 的重试次数次方”和“上限值”之间取较小者,既给恢复留时间,又不无限等待。
  1. 基础等待 T_base首次熔断后的最短睡眠时间,让下游喘一口气,比如 1 秒。
    Tbase=1 sT_{\text{base}} = 1\,\text{s}
  2. 重试指数 n第几次进入打开状态,第一次为 0,第二次为 1,以此类推。
    n=retryCountn = \text{retryCount}
  3. 指数增长 $2^n$每次重熔断等待时间翻倍,给“受了伤”的下游更充裕的修复时间。
    2n=2retryCount2^{n} = 2^{\text{retryCount}}
  4. 封顶 T_max等待时间不能无限涨,超过上限后固定取最大值,比如 30 秒。
    Twait=min⁡(Tmax,  Tbase×2n)T_{\text{wait}} = \min \left( T_{\text{max}},\; T_{\text{base}} \times 2^{n} \right)

滑动窗口:只看最近的账本

你不会因为海鲜供应商三年前送错过一批货就永远不用他,你更关心的是“最近一周”靠不靠谱。同理,熔断器不能统计“自开机以来”的失败率——十年前的一次抖动早已无关紧要,真正要盯的是最近一小段时间的表现。这就是滑动窗口的直觉。

想象后厨有个流水账本,每 1 秒为一格,记录这一秒内有哪些订单成功、哪些失败。账本只保留最近 10 格,新的 1 秒来了,最旧的那格就被撕掉。这样任何时候抬头看账本,看到的都是“最近的状况”。熔断器的滑动窗口统计就是这个账本:

固定窗口像翻旧账本——上个月的数据还在拉低平均值;滑动窗口则只看“最近这页账”,让熔断器对瞬时的异常更敏感,也不会被陈年旧账拖累判断。

Rwindow=∑i=1kfi∑i=1kniR_{\text{window}} = \frac{\sum_{i=1}^{k} f_i}{\sum_{i=1}^{k} n_i}
窗口内失败率 = 每个时间桶失败次数之和 ÷ 每个时间桶请求数之和。它比固定窗口更平滑,不会因为恰好在边界处流量突刺而误判。
  1. 分桶总数 k把窗口切成固定长度的小桶,窗口 10 秒、每桶 1 秒,则 k=10。
    k=windowSizebucketSize=10k = \frac{\text{windowSize}}{\text{bucketSize}} = 10
  2. 单桶失败数 $f_i$第 i 秒内失败的请求数,比如第 3 秒有 5 次超时,则 $f_3=5$。
    fi≥0f_i \ge 0
  3. 单桶请求数 $n_i$第 i 秒内所有请求数,成功失败都计入,且不小于 $f_i$。
    ni≥fin_i \ge f_i
  4. 聚合求失败率先分别累加所有桶的失败数和请求数,再相除,得到窗口整体失败率。
    Rwindow=∑fi∑niR_{\text{window}} = \frac{\sum f_i}{\sum n_i}

降级策略:换上备选菜单

活鲜断供了,菜单上的“清蒸石斑”怎么办?有经验的主厨会立刻换上备选菜单:用冰鲜代替活鲜、用库存的高汤做“红烧豆腐”推荐给客人、或者直接告诉客人“这道菜今日售罄”——客人吃不到石斑,但餐厅还在正常运转。这就是降级:主路径不可用时,用次优方案保住整体体验。

微服务降级的常见姿势有四种。默认值兜底:拿不到真实数据就返回一个安全的默认值,比如库存接口挂了就返回“库存充足”;缓存回退:从本地或 Redis 缓存取旧数据,虽然不够新但总比没有强;异步降级:把非核心的写操作丢进消息队列,让请求快速返回“已受理”;主动失败降级:明确告诉调用方 “请稍后重试”,保护系统不被拖垮。

降级不是“免费午餐”,它也有成功率。主路径成功率 $P_{\text{primary}}$ 越高,需要降级的概率越低;一旦降级,兜底路径的成功率 $P_{\text{fallback}}$ 决定了最终体验。综合可用性 A 是两者的加权组合:

备选菜单不能乱上:冰鲜冒充活鲜要提前告知,缓存数据过期要有标记。降级的价值不只是“系统没宕机”,更是让用户体验与数据一致性在可接受的范围内软着陆。

A=Pprimary+(1−Pprimary)×PfallbackA = P_{\text{primary}} + \left( 1 - P_{\text{primary}} \right) \times P_{\text{fallback}}
整体可用性 = 主路径成功概率 + 主路径失败概率 × 兜底成功概率。失败不可怕,可怕的是失败后没有好的 B 计划。
  1. 主路径成功率 $P_{\text{primary}}$正常调用下游服务成功的概率,数值越接近 1 越健康。
    Pprimary∈[0,1]P_{\text{primary}} \in [0,1]
  2. 失败概率 $1-P_{\text{primary}}$每次调用主路径不成功的比例,它决定了降级被触发的频率。
    1−Pprimary=0.21 - P_{\text{primary}} = 0.2
  3. 兜底成功率 $P_{\text{fallback}}$降级后走缓存或默认流的成功率,通常设计成接近 1。
    Pfallback≈0.99P_{\text{fallback}} \approx 0.99
  4. 综合可用性 A主路径成功与“失败后兜底成功”共同构成整体拿到结果的比例。
    A=0.8+0.2×0.99=0.998A = 0.8 + 0.2 \times 0.99 = 0.998

限流配合:门口先放号

餐厅生意爆棚时,再大的后厨也会手忙脚乱。聪明的经理会在门口放号排队:每十分钟只放五十位客人进场。这样一来,后厨的负荷稳定可控——哪怕门外再多人,店内也不会被挤爆。这就是限流:控制进入系统的流量,而不是等系统被压垮后再补救。

熔断和限流解决的阶段不同:熔断管的是“下游已经坏了”——快速失败避免故障扩散;限流管的是“系统快要挤爆了”——提前控制入口流量。二者像后厨的“进门闸机”和“供应商保险丝”一样互补:闸机控制客流,保险丝守护供应链。

最经典的限流算法是令牌桶。想象一个木桶每隔 $\Delta t$ 秒就自动掉进 $\rho$ 枚令牌,每个请求进来必须先拿一枚令牌才能“入店”。桶有一定容量 B,满了就不再积累。这样既能平滑突发流量,又能限制平均速率:

放号排队讲究“公平”和“可预期”:令牌桶给了突发流量一定弹性,漏桶则彻底削峰填谷。限流让进入系统的流量温和可控,熔断再兜底保命,两者相得益彰。

C=min⁡(C+ρ×Δt,  B)C = \min \left( C + \rho \times \Delta t,\; B \right)
当前令牌数 = 原有令牌 + 新补充令牌,但最多不超过桶容量 B。请求来了有令牌就放行,没令牌就排队或拒绝。
  1. 当前令牌数 C桶里现存的令牌数,每个请求消耗一枚令牌。
    C≥0C \ge 0
  2. 补充速率 ρ每秒向桶中放入的令牌数,即限流的目标速率,如 100 req/s。
    ρ=QPSlimit\rho = \text{QPS}_{\text{limit}}
  3. 时间增量 Δt距上次补充令牌经过的时间,时间越长,新令牌积累越多。
    Δt=tnow−tlast\Delta t = t_{\text{now}} - t_{\text{last}}
  4. 容量上限 B桶最多容纳的令牌数,防止空闲期攒下大量令牌造成瞬间突发。
    C=min⁡(C+ρ×Δt,  B)C = \min \left( C + \rho \times \Delta t,\; B \right)

舱壁隔离:船上的水密隔舱

泰坦尼克号的悲剧给我们最重要的教训是:船底被撞穿后,水会迅速从一个舱灌满所有舱。如果船体被分隔成若干个独立的水密隔舱,一个舱进水,其他舱依然浮在水面。舱壁隔离(Bulkhead)这个词,就源自造船业——它被微服务借来,防止一个服务的故障“灌满”整个系统。

后厨里同样需要舱壁:热菜档、凉菜档、烧腊档各自有独立的灶台和人员。热菜档口的供应商出问题,烧腊档不会跟着停摆——因为它们的线程资源互不共享。微服务里的实现有两种:线程池隔离,给每个下游调用分配独立的线程池,一个池被占满不影响其他池;信号量隔离,不占线程池,只限制同时进行的调用数量。两者都像隔舱板:隔离故障,限制传播范围。

舱壁隔离要配合超时预算使用。每个调用的最大耗时必须设限——就像后厨规定每道菜最多等 5 分钟,超过就换菜。有了超时,线程才不会被“无限等待”拖死。隔离配额的大小可以按利特尔法则计算:需要的线程数等于峰值流量乘以单请求耗时:

一个灶台起火,不能烧掉整个厨房。舱壁隔离加超时预算,就是给每个依赖装上“独立隔舱”和“逃生时限”——就算某个下游彻底瘫痪,也只是损失一个舱室而已。

Nthreads=⌈QPSpeak×LP99⌉N_{\text{threads}} = \left\lceil \text{QPS}_{\text{peak}} \times L_{P99} \right\rceil
线程池容量 = 峰值每秒请求数 × P99 耗时(向上取整)。太多浪费资源,太少会自己把自己限死。
  1. 峰值流量 QPS\_peak下游在高峰期每秒的最大请求数,是线程池必须接住的“浪头”。
    QPSpeak=200\text{QPS}_{\text{peak}} = 200
  2. P99 耗时 $L_{P99}$99% 的请求在多少秒内完成,代表请求的典型最慢耗时。
    LP99=0.5 sL_{P99} = 0.5\,\text{s}
  3. 乘积得到并发数每秒 200 个请求、每个占 0.5 秒,则在途请求约为 100 个。
    QPSpeak×LP99=100\text{QPS}_{\text{peak}} \times L_{P99} = 100
  4. 向上取整线程数不能是小数,且要留少量余量,用取整符保证够用。
    Nthreads=⌈100⌉=100N_{\text{threads}} = \lceil 100 \rceil = 100

实战配置:把参数调成手艺

保险丝选多粗,要结合家里电器总功率;熔断器参数也一样,没有放之四海皆准的数值。阈值设太低,正常的流量毛刺都会导致误熔断;设太高,又成了摆设。真正的大厨,会一边炒菜一边观察火候,实战调参的本质就是持续观察、持续校准。

核心参数主要有四个:失败率阈值——如滑动窗口内失败率超过 50% 就熔断;最小请求数——窗口内请求太少时不熔断,避免小样本误判;睡眠时间——打开后等待多久进入半开;半开放行上限——半开时最多允许几个试探请求。生产环境中还要配合监控大盘:成功率、熔断次数、降级比例、线程池活跃度,任何一个指标异常都要触发告警。

为什么熔断对整条链路如此重要?因为微服务链路的可用性是连乘的。调用链越长,任何一个环节掉链子都会显著拉低整体可用性:

主流的 Hystrix、Sentinel、Resilience4j 框架都是这套思想的工程化实现,配置项也大同小异。掌握了熔断原理与调参手感,无论哪个框架,你都能在关键时刻给系统装上恰到好处的保险丝。

Achain=∏i=1naiA_{\text{chain}} = \prod_{i=1}^{n} a_i
链路整体可用性 = 各环节可用性连乘。4 个 99% 的服务串联,整体只剩 96%;若某个环节跌到 80%,整条链就名存实亡了。
  1. 单环节可用性 $a_i$链路中第 i 个服务的可用性,99.9% 记为 0.999。
    ai∈[0,1]a_i \in [0,1]
  2. 连乘符号 $\prod$把所有环节可用性乘起来,环节越多,乘积越小。
    ∏i=1nai=a1×a2×⋯×an\prod_{i=1}^{n} a_i = a_1 \times a_2 \times \cdots \times a_n
  3. 链路整体可用性 $A_{\text{chain}}$4 个 99% 服务串联后的实际可用性只有约 96%。
    Achain=0.994≈0.9606A_{\text{chain}} = 0.99^4 \approx 0.9606

AI 应用场景

大模型推理服务的熔断保护

阶段:模型部署与线上推理

解决的问题:大模型推理 API 在高峰期 GPU 排队,P99 延迟从 200ms 飙升到 5s。传统做法只是把超时时间调长,结果线程全被慢请求占满,整个业务服务跟着 OOM。引入熔断器后,窗口内失败率超 40% 即熔断,降级返回“FAQ 知识库匹配结果”,主业务流程不再被模型推理拖垮。

具体案例:某智能客服平台同时服务 50 个租户,大模型偶尔因资源争抢超时。工程团队设置熔断阈值 0.4、超时 3s、半开放行 2 个请求,配合线程池隔离。压测显示,模型服务故障时整体可用性从 82% 提升到 99.5%,恢复时间从分钟级缩短到秒级。

推荐系统调用风控服务的隔离降级

阶段:服务编排与流量治理

解决的问题:推荐服务串行调用风控服务,风控发布变更时偶发超时,导致推荐主链路大量报错。通过线程池隔离为风控调用分配独立资源池,并加熔断器:连续失败 5 次熔断 10 秒,降级为本地规则引擎先放行,风控恢复后自动切回真实调用。

具体案例:某电商推荐系统双 11 当天风控策略更新引发抖动,未加熔断前推荐接口错误率 15%,接入隔离与熔断后错误率降到 0.1%,风控服务恢复后无需人工干预自动转正。

训练平台监控上报的故障自治

阶段:训练平台稳定性保障

解决的问题:训练任务上的监控 agent 每 5 秒上报指标到监控中心,监控中心频繁重启导致 agent 的 HTTP 调用全部阻塞,反而拖累训练进程。为上报链路加迷你熔断器:失败 3 次后断开上报并缓存到本地文件,监控中心恢复后再重放,训练主流程完全不受影响。

具体案例:某 MLOps 平台约 2000 个训练任务同时运行,监控中心一次故障演练中,未保护的 agent 有 30% 出现连接堆积;加上熔断与本地缓存后,训练任务零影响,指标补报完整率达 98%。

Python 演示

python84 行
"""迷你熔断器:给微服务调用装上保险丝"""import timefrom collections import deque  class CircuitBreaker:    """三状态熔断器:闭合 -> 打开 -> 半开"""     def __init__(self, fail_limit=3, sleep_seconds=1.0, half_max=1):        self.fail_limit = fail_limit          # 窗口内允许的最大失败次数        self.sleep_seconds = sleep_seconds    # 打开后等待时间        self.half_max = half_max              # 半开放行上限        self.state = 'closed'                 # closed / open / half_open        self.open_at = 0.0                    # 进入打开状态的时间戳        self.half_counter = 0                 # 半开放行计数        self.window = deque(maxlen=10)        # 滑动窗口:最近10次调用     def _record(self, ok):        self.window.append(0 if ok else 1)     def _fail_count(self):        return sum(self.window)     def _try_half_open(self):        if self.state == 'open' and time.time() - self.open_at >= self.sleep_seconds:            self.state = 'half_open'            self.half_counter = 0            return True        return False     def _turn_open(self):        self.state = 'open'        self.open_at = time.time()        self.half_counter = 0     def call(self, func, *args, **kwargs):        # 打开态:先看是否到冷却时间,未到则快速失败        if self.state == 'open':            if not self._try_half_open():                raise RuntimeError('熔断器打开,请稍后再试')         # 半开态:只放行少量请求        if self.state == 'half_open':            if self.half_counter >= self.half_max:                raise RuntimeError('半开探测中,暂缓放行')            self.half_counter += 1         # 真正执行下游调用        try:            result = func(*args, **kwargs)        except Exception:            self._record(False)            self._turn_open()            raise        else:            self._record(True)            if self.state == 'half_open':                self.state = 'closed'            if self.state == 'closed' and self._fail_count() > self.fail_limit:                self._turn_open()            return result  def flaky_service():    """模拟不稳定下游:前3秒可用,3-6秒超时,6秒后恢复"""    elapsed = time.time() - start_time    if elapsed < 3 or elapsed >= 6:        return '订单数据'    time.sleep(0.05)    raise ConnectionError('下游处理超时')  if __name__ == '__main__':    start_time = time.time()    cb = CircuitBreaker()    for i in range(30):        try:            data = cb.call(flaky_service)            print('第%02d次 成功 数据=%s 状态=%s' % (i, data, cb.state))        except RuntimeError as e:            print('第%02d次 快速失败 状态=%s' % (i, cb.state))        except Exception as e:            print('第%02d次 调用异常 状态=%s' % (i, cb.state))        time.sleep(0.3)
目的:用 60 行代码实现一个迷你三态熔断器,演示“失败累积→熔断打开→快速失败→半开探测→恢复闭合”的完整生命周期。
  1. self.window = deque(maxlen=10)滑动窗口容器,自动丢弃最旧的调用记录,只看最近表现。
  2. if not self._try_half_open(): raise RuntimeError(...)打开态未到睡眠时间直接快速失败,不碰下游。
  3. self._fail_count() > self.fail_limit窗口内失败次数超过阈值就跳到打开状态。
  4. if self.state == 'half_open': self.state = 'closed'半开放行的一个请求成功即认为下游恢复。
  5. cb.call(flaky_service)业务方像调用普通函数一样使用熔断器,失败处理完全透明。

常见误区

误区熔断器打开后,我只要用重试机制疯狂重试下游,试得够勤快总能成功一次,系统就能尽快恢复。

正解打开状态的使命就是“快速失败”,让调用方立刻拿回线程。此时重试等于把请求继续压向下游,相当于供应商断货时你每秒打一个电话催货——对方不但接不了单,反而被电话彻底淹没。正确做法是耐心等待 sleep 超时,进入半开后再试探。

为何常见:人类本能觉得“多试几次能提高成功率”,却忽略了故障场景下重试会放大流量、加剧雪崩。

误区把失败率阈值设到 0.9,这样基本上永远都不会触发熔断,请求成功率看起来很高。

正解阈值过高等于用铜丝代替保险丝——平时看着没问题,过载时该断不断,最终烧坏整个系统。阈值应结合下游容错率和业务容忍度设置,通常在 0.3~0.5。哪怕牺牲少量正常请求,也要保证故障时能及时切断。

为何常见:担心误伤正常流量,对“可用性损失”过于敏感,却低估了故障扩散带来的更大量级损失。

误区熔断后用默认值或缓存数据顶一顶,用户感知不到,等下游恢复再切回来就行。

正解降级数据会影响用户体验和数据一致性。比如库存接口返回“有货”,用户下单后却发不了货。降级时要区分场景:读接口可返回带过期标记的缓存,写接口应明确提示“稍后重试”。就像冰鲜不能冒充活鲜上桌,要提前跟客人说明。

为何常见:只看到“系统没宕机”这一面,忽略了降级数据对业务正确性和体验的隐性伤害。

误区半开就是为了尽快恢复,所以要多放一些请求过去,快速检验下游到底恢复了没有。

正解半开的核心是“试探”,放行量必须极小,且优先放行幂等、只读请求。一次放太多,脆弱的下游可能刚醒过来又被压垮,陷入反复熔断。就像抢修队合闸后先开一盏灯试试线路,绝不会把所有电器一口气全打开。

为何常见:恢复心切加上对半开状态设计意图理解不深,把“探测”误当成“放量”。

误区给下游的超时时间设 30 秒,多等等总比直接判失败好,慢一点也能返回结果。

正解超时过长,线程会被慢请求长期占用,熔断器迟迟无法触发,雪崩照样发生。快速失败是熔断的前提:超时预算要匹配业务容忍度,一般不超过 P99 耗时的 2~3 倍。餐厅等供应商 30 分钟不出菜,整条出菜线就全废了。

为何常见:把“慢”错误地当成“可用”,忽略了慢请求同样吃掉线程、阻塞后续任务。

面试题

Q1. 熔断器在下列哪个状态下会正常放行请求并调用下游?

单选题 · easy

  • A.打开状态
  • B.半开状态
  • C.闭合状态← 正确答案
  • D.静止状态

答案:C。闭合状态相当于电路正常导通,所有请求正常通过;打开会快速拒绝请求;半开只放行少量试探请求;不存在“静止状态”。就像保险丝正常时电路里的电流畅通无阻,跳闸后才会断开。

Q2. 熔断器打开之后,需要等待什么再进入半开?

单选题 · easy

  • A.人工重启
  • B.超时冷却← 正确答案
  • C.立即恢复
  • D.随机延迟

答案:B。打开状态要等一个睡眠期(sleep window),给下游喘息修复的时间,之后才能进入半开试探。超时冷却就像供应商冷库坏了要等维修队修好,不能指望立刻恢复。

Q3. 半开状态的核心作用是什么?

单选题 · mid

  • A.拒绝所有流量
  • B.放行全部请求
  • C.试探性放行少量← 正确答案
  • D.备份下游服务

答案:C。半开状态只放行少量试探请求,验证下游是否恢复。放行全部会让刚恢复的下游再次被打垮,拒绝所有又无法感知恢复。就像合闸后先点一盏灯试试线路,而不是大开所有电器。

Q4. 熔断降级中的“降级”通常是指?

单选题 · mid

  • A.降低服务器配置
  • B.返回兜底数据← 正确答案
  • C.降低代码复杂度
  • D.减少集群数量

答案:B。降级是指主路径不可用时,切换为兜底方案——返回缓存或默认值来保障基本可用,而不是降低机器配置或删代码。好比活鲜断供就换冰鲜,客人吃不上最好的,但餐厅还在营业。

Q5. 滑动窗口相比固定窗口的优势是?

单选题 · mid

  • A.计算更加快速
  • B.统计平滑准确← 正确答案
  • C.占用内存更少
  • D.代码逻辑简单

答案:B。固定窗口在窗口边界处容易出现流量突刺导致误判,滑动窗口以更细的时间桶滚动聚合,统计更平滑准确。就像看供应商最近一周的送货记录,比看“上周整周汇总”更能捕捉到临时波动。

Q6. 熔断与降级的关系是什么?

单选题 · hard

  • A.两者完全等价
  • B.熔断是降级的前提← 正确答案
  • C.降级是熔断的前提
  • D.二者互不相关

答案:B。熔断是保护手段,降级是兜底策略。熔断打开后触发降级逻辑返回兜底数据,所以熔断是降级的前提;降级不一定非要熔断,超时也可以触发。就像供应商断供(熔断)之后你才会启动备选菜单(降级)。

Q7. 订单接口下游故障触发熔断后,最佳降级方案是?

单选题 · hard

  • A.直接抛出错误
  • B.返回缓存列表← 正确答案
  • C.无限重试等待
  • D.清空所有订单

答案:B。对读接口,返回缓存列表能在不外抛错误的情况下保住页面可用;直接抛错体验差,无限重试会加剧雪崩,清空订单更是灾难。缓存兜底像餐厅用冰鲜菜顶上,客人能吃到菜,只是不最新鲜。

Q8. 熔断器包含的三种状态是什么?

多选题 · mid

  • A.闭合← 正确答案
  • B.打开← 正确答案
  • C.半开← 正确答案
  • D.挂起

答案:ABC。熔断器经典三状态是闭合(正常放行)、打开(快速拒绝)、半开(试探放行),不存在“挂起”状态。闭合到打开靠失败率触发,打开到半开靠睡眠超时,半开成功回到闭合、失败重新打开。

Q9. 半开探测阶段适合放行哪些请求?

多选题 · hard

  • A.少量请求← 正确答案
  • B.幂等请求← 正确答案
  • C.只读请求← 正确答案
  • D.大量并发

答案:ABC。半开应放极少量请求避免压垮下游,优先选幂等或只读请求,防止试探请求造成脏数据;大量并发违背了试探初衷。就像抢修后先开一盏灯,而不是一次性打开所有电器。

Q10. 下列哪些做法会加剧微服务雪崩?

多选题 · extreme

  • A.无限重试← 正确答案
  • B.超时过长← 正确答案
  • C.禁用熔断← 正确答案
  • D.线程隔离

答案:ABC。无限重试放大流量、超时过长占死线程、禁用熔断等于不设防,三者都会加速故障扩散;线程隔离是有效的防护手段。好比供应商断货时不断催单、不设等待时限、又不拉闸——后厨注定被拖垮。