1 分钟

安全的第三方 API 集成:重试、超时与断路器

在外部服务故障时保持应用运行的安全第三方 API 集成。学习超时、重试、断路器和快速检查。

安全的第三方 API 集成:重试、超时与断路器

为什么第三方 API 会阻塞你的核心工作流

第三方 API 的失败往往不是明显的“宕机”。最常见的问题是变慢:请求挂起、响应迟到,而你的应用一直在等待。如果这些调用处在关键路径上,外部的小问题会在你系统内部堆积。

这就是本地变慢如何演变成全面故障的原因。线程或工作进程被卡住等待,队列增长,数据库事务保持打开状态更久,新请求开始超时。不久之后,即便是不依赖外部 API 的页面也会感到故障,因为系统被等待中的任务压垮了。

影响是具体的。一个不稳定的身份提供商会阻塞注册和登录。支付网关超时会冻结结账,让用户不知道是否已被扣款。消息延迟会阻止重置密码和订单确认,从而引发第二波重试和支持单。

目标很简单:隔离外部失败,让核心工作流继续进行。那可能意味着允许用户下单,然后稍后确认支付,或即使欢迎邮件发送失败也允许注册成功。

一个实用的成功指标是:当某个提供方变慢或宕机时,你的应用仍能快速且清晰地响应,影响范围保持较小。例如,大多数核心请求仍在正常延迟预算内完成,失败仅限于真正依赖该 API 的功能,用户能看到明确的状态(已排队、待处理、稍后重试),并且当提供方恢复时能自动恢复。

你应该为哪些失败情况做准备

大多数失败是可预见的,尽管它们何时发生不可预测。事先列出这些情况,你就能决定哪些要重试、哪些要停止、以及向用户展示什么。

常见类别:

  • 延迟激增(请求突然变慢 10 倍)
  • 瞬时的服务器或网络错误(超时、502/503、连接重置)
  • 速率限制和配额耗尽(429、日限额)
  • 认证与权限问题(密钥过期、访问被撤销)
  • 错误或出乎意料的数据(缺字段、格式错误、部分响应)

并非所有错误都意味着同样的事情。瞬时问题通常值得重试,因为下一次调用可能成功(网络抖动、超时、502/503,以及在等待后的一些 429)。永久性问题通常不会自愈(无效凭证、错误的端点、格式错误的请求、权限拒绝)。

把所有错误一视同仁会把小事件变成停机。重试永久性失败浪费时间,更快触及速率限制,并积累会拖慢其他工作的积压。不重试瞬时失败则迫使用户重复操作并丢失本可以在片刻后完成的工作。

对那些暂停就像中断的工作流要特别关注:结账、登录、密码重置和通知(邮件/SMS/推送)。营销 API 的两秒延迟只是恼人,但支付授权的两秒延迟会阻塞收入。

一个有用的测试是:"这个调用现在必须完成用户的主要任务吗?" 如果是,你需要严格的超时、谨慎的重试和清晰的失败路径。如果不是,就把它移到队列中,让应用保持响应。

超时:选一个界限并坚持它

超时是你愿意等待的最长时间。没有明确的上限,一个缓慢的提供方会让等待的请求堆积并阻塞重要工作。

把等待分成两类会有帮助:

  • Connect timeout(连接超时):尝试建立连接的最长时间。
  • Read timeout(读取超时):连接建立后等待响应的最长时间。

选择数值不是追求完美,而是要匹配人的耐心和你的工作流。

  • 如果用户正盯着一个加载圈,你通常需要快速回应并提供明确的下一步。
  • 如果是后台作业(如夜间同步发票),可以给更多时间,但仍需上限,不能无限挂起。

选择超时时间的实用方法是从体验反推:

  • 用户能等多久你才需要显示明确信息?
  • 如果这个调用现在失败,你是否可以稍后重试或使用回退?
  • 峰值时同时运行多少此类调用?

权衡是真实的。超时太长会占用线程、工作进程和数据库连接;超时太短会产生虚假失败并触发不必要的重试。

不会恶化故障的重试策略

当失败很可能是暂时的时,重试很有用:短暂的网络问题、DNS 抖动或一次性的 500/502/503。在这些情况下,再试一次可能成功,用户不会察觉。

风险在于重试风暴。当许多客户端同时失败并同时重试时,会压垮提供方(也可能压垮你自己的工作进程)。退避(backoff)和抖动(jitter)能防止这种情况。

重试预算能让你更审慎。限制尝试次数并限定总耗时,这样核心工作流就不会一直等待别人。

一个安全的默认重试方案

  • 只重试少数次数(通常总尝试 1-3 次,具体取决于流程)。
  • 使用指数退避(例如 200ms、500ms、1s)并加上随机抖动。
  • 限制重试的总耗时(用户交互流程通常为几秒)。
  • 为每次尝试设定单独超时,而不是用一个长超时覆盖所有尝试。

不要重试可预测的客户端错误,如 400/422 校验错误、401/403 认证问题或 404。这类错误几乎总是会再次失败,只会增加负载。

另一个防护措施:只有在有幂等性措施时才重试写操作(POST/PUT),否则会有重复扣款或重复记录的风险。

幂等性:让重试对真实工作流安全

幂等性意味着多次执行同一请求,最终结果相同。这很重要,因为重试是常态:网络丢包、服务器重启、客户端超时都可能发生。没有幂等性,"有意"的重试会产生重复项并带来真实的金钱问题。

想象结账场景:支付 API 很慢,你的应用超时并重试。如果第一次调用其实已经成功,重试可能会产生第二次扣款。类似风险出现在创建订单、启动订阅、发送邮件/SMS、发起退款或创建支持单等操作中。

解决办法是为每个“执行操作”的调用附上幂等键(或请求 ID)。它应当针对用户动作唯一,而不是针对单次尝试。提供方(或你自己的服务)使用该键识别重复并返回相同的结果,而不是再次执行操作。

把幂等键当作数据模型的一部分,而不是一个可能被忘记的 header。

在生产中可靠的模式

在用户开始动作(例如点击“支付”)时生成一个键,并把它存到本地记录中。

在每次尝试时:

  • 发送相同的键。
  • 存储你收到的最终结果(成功响应、失败码、交易 ID)。
  • 如果已经有记录的结果,就返回该结果,而不是重复执行操作。

如果你是内部调用的“提供方”,在服务器端也要强制执行相同行为。

断路器:在服务故障时停止调用

拥有你生成的代码
生成集成后导出源码以便审查和自定义。

断路器是一个安全开关。当外部服务开始失败时,你短时间内停止调用它,而不是继续堆积很可能会超时的请求。

断路器通常有三种状态:

  • Closed(闭合):请求正常流通。
  • Open(打开):在冷却窗口内阻止调用。
  • Half-open(半开):冷却结束后,允许少量测试调用以检查服务是否恢复。

当断路器打开时,你的应用应当有可预测的应对方式。如果地址校验 API 在注册时宕机,就接受地址并标记稍后审核。如果支付风控检查不可用,则把订单排队做人工审核或临时禁用该选项并进行说明。

选择与用户影响匹配的阈值:

  • 连续错误(例如连续 5 次失败)
  • 短时间内高失败率
  • 许多慢响应(超时)
  • 特定状态码(如重复的 503)

把冷却期设短(几秒到一分钟),并限制半开时的探测。目标是先保护核心工作流,然后快速恢复。

回退与队列:让应用保持可用

当外部 API 变慢或宕机时,你的目标是让用户能继续进行。那就需要一个诚实的备用方案。

回退:选择“足够好”的体验

回退是 API 无法及时响应时应用的处理方式。选项包括使用缓存数据、切换到降级模式(隐藏非必需部件、禁用可选操作)、让用户手动输入以替代 API(手动填地址),或显示明确的下一步提示。

要诚实:不要在没有完成的情况下声称事情已完成。

队列:把不必现在完成的工作放后面

如果工作不必在用户请求内完成,就把它推到队列并快速响应。常见候选项:发送邮件、同步到 CRM、生成报告、上报分析事件。

对核心操作要快速失败。如果某个 API 不必完成结账(或账户创建),就不要阻塞请求。接受订单、把外部调用放入队列并稍后对账。如果某个 API 是必需的(例如支付授权),就快速失败并给出清晰信息,而不是一直让用户等待。

用户看到的状态应当与后台发生的事情匹配:清晰的状态(完成、待处理、失败)、你能兑现的承诺(现在出收据,稍后确认)、可重试的方式以及 UI 中可见的记录(活动日志、待处理标识)。

速率限制与负载:避免自找麻烦

速率限制是提供方告诉你的“你可以调用,但不要太频繁”。你会比想象中更早命中它:流量激增、后台作业同时触发,或错误导致循环重试。

从控制你产生多少请求开始。尽量批量处理,在安全的情况下缓存响应 30 到 60 秒,客户端侧节流以避免比提供方允许的更快突发。

收到 429 Too Many Requests 时,把它当作放慢节奏的信号。

  • 当提供 Retry-After 时请遵守。
  • 添加抖动以避免许多工作者同时重试。
  • 限制对 429 的重试次数,避免无限循环。
  • 对重复的 429 更激进地退避。
  • 把它记录为指标,以便在用户察觉前发现模式。

还要限制并发度。单个工作流(如同步联系人)不应占用所有工作进程并挤占关键流程如登录或结账。使用独立池或按功能限额有帮助。

逐步操作:一个安全的默认集成方案

快速添加断路器
生成断路器和冷却逻辑,防止重试风暴。

每个第三方调用都需要一个失败计划。你不需要完美,只需要在提供方糟糕时表现可预测。

1) 对调用分类(必须完成 vs 可等待)

决定如果调用现在失败会发生什么。结账时的税费计算可能是必须的;同步营销联系人通常可以等待。这个选择决定其余策略。

2) 设定超时和重试预算

为不同类型调用选定超时并保持一致。然后设定重试预算,避免对慢速 API 不断猛击。

  • 必须且用户在等待:短超时,0-1 次重试。
  • 可等待、后台作业:更长超时,几次退避重试。
  • 永远不要无限重试:为每个任务限制总耗时。

3) 用幂等性和跟踪让重试安全

若请求会创建资源或收费,添加幂等键并保存请求记录。若支付请求超时,重试不应导致重复扣款。跟踪也有助于支持判断“是否已成功”。

4) 添加断路器和回退策略

当错误激增时,短时间停止调用提供方。对于必须同步的调用,展示明确的“重试”路径;对于可等待的调用,把工作入队稍后处理。

5) 监控基础指标

跟踪延迟、错误率和断路器的开/关事件。对持续性变化报警,而非单次短暂波动。

导致小问题变成停机的常见错误

大多数 API 故障并非一开始就很严重。它们之所以变严重,是因为你的应用以最糟糕的方式反应:等待太久、重试过猛,并占用维持其他功能的相同工作进程。

这些模式会造成级联:

  • 对所有错误都重试,包括 4xx 的客户端问题(无效请求、认证过期、权限丢失)。
  • 把超时设置得很长“以防万一”,却悄悄消耗了线程、数据库连接或任务运行器,直到耗尽资源。
  • 在没有幂等性的情况下对创建操作重试,导致双重扣款、重复发货或重复记录。
  • 配置错误的断路器导致无法恢复或频繁开关抖动。
  • 把部分故障当作全部故障处理,而不是仅降级受影响的功能。

小修小补能避免大停机:仅重试可能短暂性的错误(超时、部分 429、部分 5xx),并用退避和抖动限制尝试次数;保持超时短且有目的;对任何会创建或收费的操作要求幂等性;并为部分故障设计降级策略。

上线前的快速清单

更快交付有弹性的功能
从一开始在构建、部署和托管中内置可靠性模式。

在把集成推到生产前,用失败思维做一次快速检查。如果某项你无法回答“是”,就把它当作阻止核心工作流(如注册、结账或发送消息)发布的阻断项。

  • 明确时间限制(连接超时和读取/响应超时)。
  • 重试有限(小重试预算、退避、抖动和总时长上限)。
  • 重试对真实操作是安全的(幂等键或明确的去重检查)。
  • 有断路器和备用方案(回退、降级模式或队列)。
  • 能及早发现问题(每个提供方和端点的延迟、错误率和依赖健康状况)。

如果支付提供方开始超时,正确的行为是“结账页面仍能加载,用户看到明确信息,你不会无限等待”,而不是“所有东西都挂起直到超时”。

示例:当提供方不稳定时保护结账流程

假设一个结账会调用三项服务:用于扣款的支付 API、计算税费的税务 API、以及发送收据的邮件 API。

支付调用是唯一必须同步的。税务或邮件的问题不应阻塞购买流程。

当税务 API 变慢时

如果税务 API 有时需要 8 到 15 秒,结账若等待会导致用户放弃且占用工作进程。

更安全的流程:

  • 设定一个硬超时(例如 800ms 到 2s)并快速失败。
  • 最多重试一次,仅在安全时并带抖动。
  • 若超时,使用缓存税率或上次已知的税表作为替代。
  • 若法律上不能使用缓存税率,则把订单标记为“待税务处理”并排队重新计算。

结果:在税务提供方变慢时,弃单和卡住的订单减少。

当邮件 API 宕机时

收据邮件很重要,但绝不应阻塞支付捕获。如果邮件 API 失败,断路器在几次快速失败后应打开并在短冷却期内停止调用。

不要在请求中同步发送邮件,而是把“发送收据”的任务入队并附上幂等键(例如 order_id + email_type)。如果提供方宕机,队列会在后台重试,而用户仍看到购买成功。

结果:丢失确认邮件导致的支持工单减少,且非支付原因不会导致收入流失。

接下来的步骤:在整个应用中安全推广这些做法

挑一个当它出问题最痛的工作流(结账、注册、开票),把它做成你的参考集成。然后把相同的默认值复制到其他地方。

一个简单的推广顺序:

  • 设定超时并快速失败,给出明确错误。
  • 对可重试的错误加上退避重试,但仅限重试范围。
  • 添加幂等性,防止重试双重扣款或重复创建。
  • 加入断路器,防止不良提供方把核心工作流卡住。

把默认值写下来并保持一致:一个连接超时、一个请求超时、最大重试次数、退避范围、断路器冷却时间以及哪些错误可重试的规则。

在扩展到下一个工作流前做一次故障演练。在测试环境中强制超时(或屏蔽提供方),然后确认用户看到有用的信息、回退路径工作、并且队列重试不会无限堆积。

如果你要快速构建新产品,把这些可靠性默认值做成可复用的模板是值得的。对于使用 Koder.ai (koder.ai) 的团队,这通常意味着先定义超时、重试、幂等性和断路器规则,然后在生成和迭代新服务时复用相同的模式。

常见问题

为什么缓慢的第三方 API 会拖垮我的应用?

为每次调用设置明确的连接和读取超时。面向用户的流程应设定较短的限制,然后返回清晰的结果,或将非必要工作移入队列。

哪些 API 错误应该重试?

应重试超时、连接重置和部分 5xx 错误,因为它们可能很快恢复。不要重试无效请求、错误凭据、权限错误或不存在的端点。

API 请求应该重试多少次?

重试次数应保持较低,通常总共尝试一到三次。使用带随机抖动的指数退避,并限制重试所花费的总时间。

支付请求超时时,如何防止重复扣款?

对创建记录、发送消息、发放退款或扣款的操作使用幂等键。同一用户操作的每次尝试都重复使用同一个键。

熔断器有什么作用?

熔断器会在反复失败或超时后暂时停止调用。它能保护你的工作进程,并让服务提供商恢复,之后再发送有限的测试请求。

哪些工作应该移到后台队列?

通过隔离可选调用,让结账和登录保持响应迅速。将收据邮件、CRM 更新和分析任务放入队列,不要在用户请求期间等待它们完成。

我的应用应该如何处理 429 限流响应?

如果服务提供商返回 Retry-After 值,请遵守它,降低并发量,并在重试前加入抖动。缓存或批量处理安全的读取操作,让应用发起更少的调用。

外部服务失败时,用户应该看到什么?

展示真实状态:已完成、处理中或失败。为用户提供实际的下一步,例如重试付款,或稍后查看仍在处理的订单。

我应该监控第三方 API 的哪些指标?

按每个服务提供商和端点跟踪延迟、错误率、超时率、限流响应和熔断器事件。对持续变化发出告警,以便及早发现问题。

发布前如何测试 API 集成?

测试超时、部分响应、429、5xx 错误和重复的写入请求。确认回退机制有效、队列任务规模受控,并且重试不会重复执行工作。

Related posts