阿杰是一家外卖餐厅的调度员,晚高峰时他同时盯着几十个骑手的配送状态。今天他遇到了头疼的事:3号骑手送到写字楼楼下,客户电话打不通;7号骑手在小区门口被保安拦住;12号骑手刚出发就爆了轮胎。阿杰一开始的应对很简单——让骑手拼命重试:再打一次电话、再按一次门铃、再绕一次路。结果电话打通了却把客户惹烦了,门铃按多了邻居投诉,绕路绕进了晚高峰的死胡同。他这才明白:不会失败的不是好系统,不会优雅地失败的系统才是灾难。 一个 AI Agent 调用工具,和外卖调度员的处境一模一样。Agent 让工具去取数据、发消息、查订单,就像派出骑手送餐。网络超时、服务限流、接口抖动,都是“暂时敲不开的门”。而错误处理与重试,就是一套关于“何时再敲门、等多久再敲、敲到第几次该收手、收手后怎么办”的完整功夫。本文从重试策略、指数退避、抖动、熔断、半开恢复到兜底告警,带你搭起一套生产级的容错体系。

岗位关联

对 AI 从业者而言,这是让 Agent 从“能跑通 demo”走向“扛得住生产环境”的分水岭。掌握它,你的 Agent 才能在网络抖动、服务雪崩面前依然稳如老狗,而这正是企业级 AI 系统最稀缺的工程能力。

目录
  1. 可视化演示
  2. 一、错误分类:先分清“该不该再敲门”
  3. 二、重试策略:约定“敲几次门、每次隔多久”
  4. 三、指数退避:越失败,等得越久
  5. 四、抖动机制:别让所有外卖员同时敲门
  6. 五、熔断保护:连败太多就挂免战牌
  7. 六、半开恢复:悄悄试一单小的
  8. 七、兜底与告警:守好最后一道防线
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示错误处理与重试的核心机制,通过开始/执行/监控/完成四种模式展示关键概念与交互流程。

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

错误处理与重试:生产级容 生产级容错体系总览与重试策略设 指数退避与抖动算法实现 核心机制可视化 错误处理与重试整体架构与关键组件

一、错误分类:先分清“该不该再敲门”

阿杰被客户投诉后冷静下来,开始把失败分成两类。一种是“客户在洗澡没听到门铃”,等几分钟再敲门就能送到;另一种是“地址写成了月球”,你敲一百次门也没用。外卖小哥不可能每次失败都盲目重试,他要先判断错误是哪一种。Agent 调用工具同样如此:工具返回的错误里,有的是暂时性的,有的是确定性的。

对应到技术层面,可重试错误包括网络连接超时、HTTP 503 Service Unavailable、429 限流——它们意味着服务还在,只是暂时忙或者网络抖了一下。不可重试错误则包括 400 参数非法、401 未授权、404 资源不存在、业务校验失败——这些错误无论重试多少次,结果都一样,因为问题出在请求本身。如果对不可重试错误也傻傻重试,只会浪费时间和算力,比如让外卖小哥对着错误的地址反复跑十趟。

可是现实中并不总是这样黑白分明,这时候就要算一笔账。重试是否划算,可以用期望收益来衡量:

阿杰把这两种错误贴在了调度台前:“暂时失败,稍后再试;确定失败,立刻换方案。”那之后,骑手们不再对着错误地址白跑,餐厅的配送准时率反而上去了。这就是错误分类的力量——重试不是无脑重复,而是有判断的再尝试。

E=p×V−C\displaystyle E = p \times V - C
这个公式帮你判断“这一单值不值得再敲一次门”:成功概率乘以成功价值,减去重试的成本,结果大于零才值得重试。
  1. p 表示重试成功的概率这是对服务“只是暂时抖动还是真的坏了”的估计
    pp
  2. V 表示成功完成任务的价值这次工具调用对用户最终目标的分量有多大
    VV
  3. C 表示一次重试的成本包括时间占用、算力消耗,以及可能重复触发的副作用
    CC
  4. 判断条件期望收益大于零,才值得再敲一次门
    E=p×V−C>0E = p \times V - C > 0

二、重试策略:约定“敲几次门、每次隔多久”

阿杰发现,就算判断出“客户应该在家”,也不能一直敲门。敲一次没应,等 30 秒再敲一次;还没应,等 1 分钟再敲一次;再不应,就打电话。这个“敲几次、隔多久”的约定,就是重试策略。Agent 对工具的每一次重试,同样要有明确的次数上限和间隔规则。

首先,最大重试次数要设置得克制。生产经验里通常 2-3 次就够:第一次重试能解决绝大多数瞬时故障,第二次能覆盖大部分网络恢复场景,第三次之后成功率提升已经非常有限,反而会把负载放大好几倍。其次,重试间隔不能太短也不能太长——太短等于没等,服务还没来得及恢复;太长则让用户等到绝望。最后,重试会占用整体时间预算,得提前算好总账。

一次工具调用的完整时间预算,就是所有尝试耗时的总和:

这里还有一个容易忽略的前提:幂等性。幂等可以这样理解——客户点了一份宫保鸡丁,你因为没收到确认而重复派了三次餐,但客户只吃一份、只付一份钱。如果后端逻辑被重复执行会产生不同结果(比如重复扣款、重复下单),那么重试本身就是事故源。所以生产级系统会给每次请求带上唯一的 request_id,让服务端识别“这单我已经处理过了”,从而安全地去重。

阿杰后来给每个订单加了一串唯一的配送编号,骑手重复敲门时,客户只需要说“我已经签收过了”就能结束这次对话。重试策略的真正艺术,是把“不放弃”和“不打扰”同时装进一套规则里。

T=t0+∑i=1N(wi+ti)≤Tmax⁡\displaystyle T = t_0 + \sum_{i=1}^{N}(w_i + t_i) \leq T_{\max}
所有尝试的时间加起来,必须小于用户能接受的等待上限,否则重试反而让用户体验更差。
  1. t0 表示首次请求的耗时第一次点击“发送”到收到响应的时间
    t0t_0
  2. N 表示最大重试次数提前约定好最多再敲几次门
    NN
  3. wi 表示第 i 次重试前的等待间隔每次敲门之间的休息时间
    wiw_i
  4. ti 表示第 i 次重试本身的耗时敲门之后到得到回应的时间
    tit_i
  5. 总预算约束总耗时不得超过用户可接受的等待上限
    ∑i=1N(wi+ti)≤Tmax⁡−t0\sum_{i=1}^{N}(w_i + t_i) \leq T_{\max} - t_0

三、指数退避:越失败,等得越久

阿杰发现,晚上 8 点到 10 点是小区门禁最繁忙的时候,很多骑手会在同一栋楼下反复刷门禁。他定了一条新规则:第一次刷不开,等 30 秒;第二次刷不开,等 1 分钟;第三次,等 2 分钟——每次等待时间翻倍。这就是指数退避(Exponential Backoff)。

为什么不是每次都等同样久?因为失败往往意味着服务端正处于过载或恢复期。如果每次都隔同样短的时间去重试,等于在服务最脆弱的时候持续施压。而指数退避让重试频率随时间快速降低,给服务留出喘息空间。它的核心公式非常简单:

这里的参数选择很讲究。初始间隔决定了系统恢复的速度:设得太短(比如 10ms),第一次重试可能撞上还没恢复的故障;设得太长(比如 10 秒),用户等待太久。常见实践是 100ms 到 1 秒之间起步。封顶上限则保护了用户体验和任务整体时间预算,一般设为 8 秒到 30 秒。如果把退避曲线的参数画出来,“陡峭上涨然后撞到天花板的曲线”就是指数退避的形态。

阿杰在调度台里给每个小区设置了一个“最长等待 3 分钟”的封顶,之后无论刷多少次门禁都不再等了。他笑着对骑手们说:“等得越久,说明门越难开;门越难开,越要冷静下来想别的办法,而不是越敲越急。”

wn=min⁡(w0×2n,  wmax⁡)\displaystyle w_n = \min(w_0 \times 2^{n},\; w_{\max})
第 n 次重试的等待时间 = 初始间隔 × 2 的 n 次方,但不得超过封顶上限。
  1. w0 表示初始等待间隔第一次失败后等待多久再敲门
    w0w_0
  2. n 表示已经失败的次数从 0 开始计数,每失败一次就加一
    nn
  3. 2^n 是指数增长因子让等待时间按 2、4、8、16 快速翻倍
    2n2^{n}
  4. wmax 是封顶上限防止等待时间无限膨胀,比如最多等 8 秒
    wmax⁡w_{\max}

四、抖动机制:别让所有外卖员同时敲门

指数退避用了一段时间,阿杰又发现一个诡异的现象:某天晚上,同一栋楼的 6 个订单全部超时,而且超时时间惊人地一致。查了日志才发现,6 个骑手都遵循“30 秒、60 秒、120 秒”的等待节奏,结果每次重试都像约好了一样同时刷卡、同时挤在门口,把门禁系统撞出了新的故障。这就是同步风暴(Thundering Herd)——当大量客户端用完全相同的退避节奏重试时,它们会同时冲击服务。

解决办法是在退避时间上加入随机扰动(Jitter),让每个客户端的重试时刻不一样。最常用的策略是全抖动(Full Jitter):等待时间不取计算出的那个确定值,而是在 0 到该值之间随机取一个数:

除了全抖动,还有等量抖动(Equal Jitter):把退避时间对半切开,前半段固定等待,后半段加随机,表达式是 cap/2 + random(0, cap/2)。它保证了至少等待一半的时间,又保留了随机性,适合对“最早重试时刻”有要求的场景。全抖动简单粗暴、分散效果好;等量抖动更温和、能保证基础退避。选哪种取决于你的系统对“过早重试”有多敏感。

阿杰给调度系统加了一个“随机小闹钟”:每个骑手的重试等待时间在规则基础上上下浮动。从此之后,门禁再也没在晚高峰被大家同时挤爆过。抖动看起来是在“破坏规则”,但恰恰是这点不精确的随机,让整体系统更加稳定。

w=random(0,  min⁡(w0×2n,  wmax⁡))\displaystyle w = \mathrm{random}(0,\; \min(w_0 \times 2^{n},\; w_{\max}))
每次重试的等待时间,从 0 到指数退避上限之间随机抽一个数,让节奏彻底错开。
  1. 先按指数退避算出等待上限 cap这是本次重试允许等待的最长时间
    min⁡(w0×2n,  wmax⁡)\min(w_0 \times 2^{n},\; w_{\max})
  2. random(0, cap) 在 0 到 cap 之间均匀随机取值每个客户端抽到的数字不同,重试时刻就自然分散了
    random(0,  cap)\mathrm{random}(0,\; cap)

五、熔断保护:连败太多就挂免战牌

就算有了重试和抖动,阿杰还是遇到过一次大事故:某小区门口修路,所有骑手送到门口都进不去。大家按照退避规则一遍遍地试,结果一晚上几十个骑手全堵在那一条路上,其他区域的订单反而没人送。阿杰恍然大悟——重试本身也会放大故障,必须有一个机制在“失败到一定程度”时彻底停下来。

这个机制就是熔断(Circuit Breaker),它像一个电路保险丝:当连续失败率超过阈值,就主动跳闸,后续请求不再真正发出,而是快速失败。熔断器内部是一个三态状态机:关闭(Closed)→ 打开(Open)→ 半开(Half-Open)。关闭时请求正常放行;打开时请求一律拦截;半开时放行少量探测请求。触发熔断的判定基于滑动窗口内的失败率:

熔断打开后的快速失败非常关键:请求不再浪费在网络往返上,而是立即返回一个“服务暂不可用”的错误,让 Agent 可以马上走兜底逻辑,把资源留给真正有能力处理的任务。这就像阿杰发现某小区进不去后,立刻在系统里挂了“免战牌”:新订单不再派往该小区,已经堵在路上的骑手立刻撤离,而不是继续死磕。

阈值怎么定?太敏感会把偶发抖动误判为故障;太迟钝又保护不了下游。一般从连续失败次数(如 5 次)或窗口失败率(如 60% 窗口内 50% 失败)起步,再根据线上监控调整。熔断不是惩罚,而是战略性的撤退——承认暂时打不下来,把资源留给能打赢的仗。

r=NfailNtotal≥θ\displaystyle r = \frac{N_{\mathrm{fail}}}{N_{\mathrm{total}}} \geq \theta
在一个统计窗口内,失败请求数占总请求数的比例达到阈值(比如 50% 或连续 5 次失败),熔断器就打开。
  1. Nfail 是窗口内的失败请求数比如最近 30 秒里失败的调用次数
    NfailN_{\mathrm{fail}}
  2. Ntotal 是窗口内的总请求数同一个时间窗里发出的全部调用次数
    NtotalN_{\mathrm{total}}
  3. r 是失败率失败次数除以总次数,连续故障时这个值会快速逼近 1
    rr
  4. θ 是熔断阈值失败率超过它,熔断器立刻跳闸
    θ\theta

六、半开恢复:悄悄试一单小的

熔断打开的“免战牌”不能永远挂着。阿杰发现,小区修路三天后通车了,但因为是“免单区”,所有订单还是绕道走,客户怨声载道。他需要一种方式来确定“这个小区到底恢复没有”。于是他定了个规矩:隔一段时间,派一个骑手带一单小订单去试试。如果送成功了,就恢复正常派单;如果又失败了,继续挂免战牌。这个“试一单”的过程就是半开(Half-Open)状态。

半开状态的核心价值,是用小成本试探大服务。它不会放行全部流量(那等于让刚恢复的服务再次被压垮),而是只放行一个或一小批探测请求。这个探测请求成功,说明服务已经恢复,熔断器可以关闭;失败则说明服务还很脆弱,熔断器回到打开状态,重新开始冷却计时。半开恢复是否成功的判定,用探测成功率来衡量:

这里还有一个关键参数:冷却时间(Cooldown)。熔断打开之后,不能立刻进入半开,要等一段冷却时间(比如 30 秒到 2 分钟)。这是因为服务故障后往往需要一段时间恢复,过早试探只会再次失败。冷却时间一到,熔断器自动转入半开,开始放探测请求。

阿杰发现,“试一单小的”这个策略妙极了:通车第一天,他派了 3 个骑手去试了 3 次,全部成功,于是系统自动恢复了该小区的派单。半开恢复的智慧在于,它把“信任”变成了一个可验证的过程——每一次探测,都是对系统状态的一次投票。

p=NokNprobe≥pmin⁡\displaystyle p = \frac{N_{\mathrm{ok}}}{N_{\mathrm{probe}}} \geq p_{\min}
半开期间放行的探测请求中,成功比例达到设定下限(比如一次探测成功即达标),熔断器就回到关闭状态。
  1. Nprobe 表示半开期间放行的探测请求数通常先放 1 个,验证服务是否恢复
    NprobeN_{\mathrm{probe}}
  2. Nok 表示其中成功的探测请求数这一单外卖有没有顺利送达
    NokN_{\mathrm{ok}}
  3. p 是探测成功率成功数除以探测总数
    pp
  4. pmin 是恢复阈值达到这个值(比如 1.0 或 0.8),熔断器关闭,全量放行
    pmin⁡p_{\min}

七、兜底与告警:守好最后一道防线

所有手段都用尽之后,最坏的情况还是发生了:小区持续封闭,熔断开了、探测失败,订单怎么都送不进去。这时阿杰会做什么?他会给客户打电话道歉、退还配送费、推荐客户换一家餐厅——总之,绝不会让客户干等着。对于 Agent 来说,这就是兜底(Fallback)与告警(Alerting):重试和熔断都解决不了问题时,必须有优雅的退出方案。

降级方案是第一选择:调用备用工具、使用缓存数据、走人工审核流程。比如天气 API 挂了,就返回昨天的天气数据并标注“数据可能延迟”。如果连降级都没有合适的,就返回默认安全值:例如风险评分接口失败时,返回一个保守的“需人工审核”结果,而不是让整个流程崩溃。同时,告警必须立刻响起——但要分级:

最后别忘了日志追踪。日志不是写给机器看的,而是写给明天凌晨背锅的自己看的。每一条失败和重试,都要记录下错误类型、重试序号、退避时长、最终是否成功、是否触发兜底。这些数据不仅是排查问题的线索,也是未来调优重试参数、熔断阈值的依据。阿杰在调度台留了一本“配送事故簿”,每场事故都记下时间、原因、处理方式,三个月后他赫然发现,大部分问题其实早已被这本簿子预言过了。

兜底和告警,是容错体系的最后一道防线。它告诉我们:即便一切努力都失败了,系统依然要体面地失败——把损失降到最低,把原因讲清楚,把改进方向留给明天。

L={INFOn<3WARN3≤n<5CRITICALn≥5\displaystyle L = \begin{cases} \mathrm{INFO} & n < 3 \\ \mathrm{WARN} & 3 \le n < 5 \\ \mathrm{CRITICAL} & n \ge 5 \end{cases}
失败次数不同,通知的级别不同:小波动写日志即可,频繁失败要发告警,系统级故障要立刻通知值班工程师。
  1. n 表示连续失败的次数一段时间内工具调用持续失败的累计数
    nn
  2. 3 次以内记为 INFO属于正常波动,记录日志追踪即可
    INFO(n<3)\mathrm{INFO} \quad (n < 3)
  3. 3 到 5 次记为 WARN需要关注,通过监控面板或即时消息提醒
    WARN(3≤n<5)\mathrm{WARN} \quad (3 \le n < 5)
  4. 5 次以上记为 CRITICAL属于重大故障,必须立即电话/短信通知值班人员
    CRITICAL(n≥5)\mathrm{CRITICAL} \quad (n \ge 5)

AI 应用场景

场景一:智能客服 Agent 调用订单查询 API

阶段:工具调用(工具选择与执行)

解决的问题:订单查询服务在高峰时段经常超时或返回 503。传统做法是让 Agent 报“查询失败”,用户体验极差。引入指数退避重试 + 熔断后,Agent 遇到瞬时失败会快速重试,失败次数多了自动熔断并走缓存兜底,用户几乎感知不到后端波动。

具体案例:某电商智能客服,用户问“我的包裹到哪了”,Agent 调用物流 API 第一次超时,200ms 后退避重试成功拿到轨迹。若物流 API 连续失败 5 次,熔断打开,Agent 不再反复调用,而是返回“物流信息暂时无法获取,已为您刷新,请稍后再试”,同时触发 WARN 告警通知值班工程师。

场景二:自动化运维 Agent 批量重启云服务器

阶段:推理与执行(批量工具调用)

解决的问题:运维 Agent 需要对 200 台服务器执行重启操作。如果某一区域网络抖动导致大量调用失败,所有 Agent 会同时按相同节奏重试,形成同步风暴,直接把云 API 打挂。全抖动策略让每台服务器的重试时刻随机化,熔断保护让问题区域快速隔离。

具体案例:某运维 Agent 在批量重启时遇到 5 台服务器调用超时,每台服务器的重试等待时间在退避基础上加上了随机抖动,重试时间点自然错开。区域失败率达到阈值后熔断打开,剩余服务器停止重试,失败清单进入死信队列,等待人工确认后二次执行。

场景三:金融风控 Agent 调用外部征信接口

阶段:数据采集(第三方服务调用)

解决的问题:征信接口按次计费且对重复请求敏感。重试一旦重复触发扣费和查询次数,会造成资损和合规风险。幂等请求 ID + 指数退避 + 半开探测,既保证了一次调用只计费一次,又能在服务恢复后快速恢复查询能力。

具体案例:某银行风控 Agent 审批贷款时调用外部征信接口,网络波动导致超时。Agent 携带同一 request_id 重试两次,服务端识别到相同 ID 直接返回首次结果,未重复扣费。接口连续失败后熔断打开,Agent 降级为返回“评分区间 + 人工审核”的保守结果,并发出 CRITICAL 告警。

Python 演示

python70 行
import randomimport time  class CircuitBreaker:    def __init__(self, fail_threshold=3, cooldown=0.02):        self.fail_count = 0        self.fail_threshold = fail_threshold        self.cooldown = cooldown        self.state = 'closed'        self.last_fail_time = 0.0     def allow(self):        if self.state == 'closed':            return True        if self.state == 'half_open':            return True        if time.time() - self.last_fail_time > self.cooldown:            self.state = 'half_open'            return True        return False     def record(self, ok):        if ok:            self.state = 'closed'            self.fail_count = 0            return        self.fail_count += 1        if self.fail_count >= self.fail_threshold or self.state == 'half_open':            self.state = 'open'            self.last_fail_time = time.time()  def exponential_backoff(attempt, base=0.01, cap=0.5):    wait = min(base * (2 ** attempt), cap)    return random.uniform(0, wait)  class FlakyService:    def __init__(self):        self._calls = 0     def invoke(self):        self._calls += 1        if self._calls <= 4:            raise ConnectionError('service timeout')        return {'data': 'ok'}  def call_tool(service, attempts=6):    breaker = CircuitBreaker()    for i in range(attempts):        if not breaker.allow():            time.sleep(0.01)            continue        try:            result = service.invoke()            breaker.record(True)            return result        except Exception as exc:            print('attempt', i + 1, 'failed:', exc)            breaker.record(False)            wait = exponential_backoff(i)            time.sleep(wait)    return None  if __name__ == '__main__':    result = call_tool(FlakyService())    print('final result:', result)
目的:完整演示了“失败 → 指数退避 → 熔断 → 冷却 → 半开探测 → 成功关闭”的完整容错闭环,可直接运行。
  1. wait = min(base * (2 ** attempt), cap)指数退避核心,等待时间随重试次数翻倍并封顶
  2. return random.uniform(0, wait)全抖动,把确定性的等待时间变成随机区间
  3. if not breaker.allow(): time.sleep(0.01); continue熔断打开时快速失败,不真正调用服务
  4. if self.fail_count >= self.fail_threshold or self.state == 'half_open'达到阈值或半开探测失败时,熔断打开
  5. if time.time() - self.last_fail_time > self.cooldown冷却时间结束,自动转入半开状态
  6. breaker.record(True); return result探测成功,熔断关闭并立即返回结果

常见误区

误区反正网络会抖动,我设置 10 次甚至 20 次重试,多试几次总能成功一次吧?最多就是多等一会儿。

正解重试会放大流量。当 100 个客户端各自重试 20 次时,就是 2000 次额外的请求,足以把本已过载的服务彻底压垮。生产环境的经验值是 2-3 次重试,且必须配上指数退避和熔断。就像外卖小哥拼命敲门 20 次,门没开反而把整层楼的邻居都惊动了。

为何常见:人的本能觉得“多试几次就能成”,却忽略了当失败者众多时,重试本身会变成新的故障源。

误区退避公式算出来的等待时间很科学,加一个随机数反而让间隔不精确,所以不加抖动。

正解不加抖动的退避是“精确的灾难”——所有失败的客户端都会按完全相同的节奏同时重试,形成同步风暴,把服务再一次击穿。全抖动把重试时刻在区间内打散,让每个客户端各自为战。随机不是不精确,而是更高级的协调。

为何常见:退避公式是确定性的,看起来“有规律、可靠”,初学者容易把随机性误认为“失控”。

误区服务可能已经恢复了,熔断还开着会误杀请求,所以熔断一打开就应该立刻重置回关闭状态。

正解熔断打开必须经过一段冷却时间,再通过半开探测来验证服务真的恢复。服务刚恢复时最脆弱,立刻放全量流量会导致立刻再次崩溃,反而延长故障时间。就像病人刚刚苏醒,不能马上让他下床跑马拉松。

为何常见:急于恢复可用性,却忘了熔断的核心价值不是“恢复”,而是“保护下游不被二次伤害”。

误区只要日志记好了,重试全失败就失败吧,反正告警系统会通知人,之后再说。

正解用户不关心你的重试策略,只关心他的问题有没有被解决。重试耗尽后必须有兜底:返回缓存数据、降级方案或安全默认值,同时触发分级告警。外卖送不到,第一时间打电话告知客户并提供退款,永远比让客户干等要强。

为何常见:工程师习惯从后端视角思考“调用”而非“用户体验”,忽视了 Agent 每一次失败都直接面对真实用户。

面试题

Q1. 以下哪种错误通常适合自动重试?

单选题 · easy

  • A.网络超时← 正确答案
  • B.参数非法
  • C.权限不足
  • D.认证失败

答案:A。网络超时属于瞬时故障,服务本身还在,等一会儿重试往往能成功。参数非法、权限不足、认证失败都是确定性错误,问题出在请求本身,重试一万次结果也一样,就像地址写错了,换十个骑手也送不到。

Q2. 指数退避的核心思想是什么?

单选题 · easy

  • A.每次重试等待时间固定
  • B.等待时间随次数指数增长← 正确答案
  • C.等待时间随次数逐渐缩短
  • D.失败后不等待立即重试

答案:B。指数退避让等待时间按 2 的幂次增长(初始 100ms、200ms、400ms…),给服务恢复留出越来越大的喘息空间。固定间隔起不到退让作用,缩短等待会加剧压力,立即重试则等于没退避。

Q3. 重试必须满足的一个重要前提是什么?

单选题 · easy

  • A.请求操作具备幂等性← 正确答案
  • B.需要持有长期密钥
  • C.必须使用 GET 方法
  • D.可以容忍部分成功

答案:A。幂等性保证重复执行不产生额外副作用,是安全重试的前提。如果重试会导致重复扣款或重复下单,那么重试本身就是事故。密钥、GET 方法、部分成功都不是重试的必要条件。

Q4. 使用抖动(Jitter)的主要目的是什么?

单选题 · mid

  • A.加快整体重试速度
  • B.使请求序列更美观
  • C.打散客户端重试节奏← 正确答案
  • D.增加额外日志信息

答案:C。如果没有抖动,所有客户端按相同退避节奏重试,会同时冲击服务形成同步风暴。抖动让每个客户端的重试时刻随机分散,就像让外卖小哥们不要约好同一秒按门铃。

Q5. 熔断器处于打开(Open)状态时,后续请求会如何处理?

单选题 · mid

  • A.继续重试直到成功
  • B.快速失败不再调用← 正确答案
  • C.转为同步阻塞等待
  • D.随机挑选少量放行

答案:B。熔断打开意味着下游已经过载或故障,继续请求只会火上浇油。此时请求应快速失败,直接返回错误,让 Agent 立刻走兜底逻辑。阻塞等待和随机放行会让熔断失去保护意义。

Q6. 半开(Half-Open)状态允许什么请求通过?

单选题 · mid

  • A.放行所有类型的请求
  • B.只放行所有写请求
  • C.放行少量探测请求← 正确答案
  • D.放行高优先级请求

答案:C。半开状态的目的是用小成本试探服务是否恢复,所以只放行少量探测请求(通常 1 个)。全部放行会让刚恢复的服务再次被压垮;只放行写请求或高优先级请求都不是半开机制的定义。

Q7. 等待时间封顶(Cap)的作用是什么?

单选题 · mid

  • A.保证退避指数基数
  • B.防止等待无限膨胀← 正确答案
  • C.加速熔断器打开
  • D.提高抖动随机程度

答案:B。指数退避如果不封顶,等待时间会无限增长,最终超过任务的总时间预算,让用户等不起。封顶让等待时间撞到天花板后不再增加,保证退避既有效又可控。

Q8. 以下哪些属于熔断器的标准状态?

多选题 · mid

  • A.关闭态可放行← 正确答案
  • B.打开态快速失败← 正确答案
  • C.半开态探针探测← 正确答案
  • D.暂停态完全停止

答案:ABC。熔断器标准三态是关闭(正常放行)、打开(快速失败)、半开(探测恢复)。“暂停态”不是熔断器的标准状态,熔断与暂停不同,熔断是自我保护机制,有明确的探测恢复路径。

Q9. 哪些是盲目重试可能带来的风险?

多选题 · hard

  • A.造成重复扣款事故← 正确答案
  • B.放大系统整体流量← 正确答案
  • C.产生冗余请求日志
  • D.掩盖真实错误原因← 正确答案

答案:ABD。非幂等操作重试会造成重复扣款;大量客户端同时重试会放大流量造成雪崩;重试成功还可能掩盖底层问题的真实原因。日志冗余是次要问题,可以通过结构化日志缓解,并非核心风险。

Q10. 重试彻底失败后,正确的兜底姿态有哪些?

多选题 · mid

  • A.返回安全默认值← 正确答案
  • B.降级调用替代接口← 正确答案
  • C.无限重试直到成功
  • D.记录日志并分级告警← 正确答案

答案:ABD。兜底的目的是优雅失败:返回安全的默认值让流程继续,降级到替代方案尽量满足用户需求,同时记录日志并触发分级告警。无限重试是之前提到的主要风险源,绝不能作为兜底手段。