接口挂了不能等产品先炸 —— 我给团队定的重试与降级接入规范
作为小团队的管理者,我最近半年最深的体会是:接入第三方 AI 接口不难,难的是接口出问题时团队知道该做什么。上个月我们主用的大模型接口连续抖动了四十分钟,重试逻辑写死的同事在群里反复问“现在能不能切”,而提前接好降级链路的同事一句话没说——系统自己扛过去了。这件事之后,我干脆把「错误重试与降级策略」写成了团队规范,这篇文章就是这份规范的核心内容。
一、先明确:重试和降级解决的是两类问题
很多团队把两者混在一起谈,协作时容易出乱子。我的划分很简单:
- 重试解决瞬时故障:网络抖动、偶发超时、限流。特点是“等一下再试,大概率能成”。
- 降级解决持续故障:服务长时间不可用、配额耗尽。特点是“再等也没用,换一条路”。
对应到流程上,重试逻辑属于每个调用点的代码职责,降级策略属于整个服务的架构职责。让写业务代码的人自己决定“什么时候放弃、放弃后用什么兜底”,风险很高——不同人写出的策略五花八门,出了事故没人说得清线上行为。所以我要求:重试参数统一配置,降级链路统一在一个模块里维护。
二、风险控制的三条红线
在写代码之前,我给团队立了三条不可协商的规则:
- 只重试幂等且安全的失败。 超时可以重试;明确返回“参数错误”的请求重试一百次结果也一样,还浪费配额。
- 必须加指数退避和上限。 固定间隔的密集重试,等于在对方服务故障时再踩一脚。退避加上抖动(jitter),避免多个实例同步重试形成尖峰。
- 降级链路要提前实测。 兜底模型不能只在配置文件里躺着,每个月要在预发环境真实跑一遍,否则降级开关一打开才发现备用 Key 早就过期了——这种事故我见过。
另外一条管理层面的要求:每次触发降级都要有记录、有告警。降级不是终点,它是“该有人来看一眼了”的信号。
三、接入实现:以 ThisToken.AI 为例
我们把 AI 接口统一收敛到 ThisToken.AI 的网关上,好处是主模型和降级模型走同一个 base_url,切换时只改模型名,不用维护两套 SDK 配置。
第一步,团队管理员去注册账号(注册后建议直接把 Key 交给指定负责人管理,不要散落在个人手里——这部分我们之前的 Key 管理规范里已经写过,这里不展开);第二步,在控制台创建 API Key,按环境分开(测试环境一把、生产环境一把);第三步,把下面的代码跑通。
这是一个带完整重试与降级逻辑的 Python 示例,可直接复制运行(价格相关问题以官网价格页为准,本文不涉及具体数字):
import os
import time
import random
import httpx
BASE_URL = "https://api.thistoken.ai/v1"
API_KEY = os.environ["THISTOKEN_API_KEY"]
# 主模型与降级模型,走同一个网关
MODEL_CHAIN = ["gpt-4o", "gpt-4o-mini"]
RETRYABLE_STATUS = {408, 429, 500, 502, 503, 504}
def chat(prompt: str) -> str:
last_error = None
for model in MODEL_CHAIN:
for attempt in range(4): # 每个模型最多 4 次
try:
resp = httpx.post(
f"{BASE_URL}/chat/completions",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"model": model,
"messages": [{"role": "user", "content": prompt}],
},
timeout=30,
)
if resp.status_code == 200:
return resp.json()["choices"][0]["message"]["content"]
if resp.status_code not in RETRYABLE_STATUS:
# 参数错误等,重试无意义,直接换下一档模型
break
last_error = f"HTTP {resp.status_code}"
except (httpx.TimeoutException, httpx.TransportError) as e:
last_error = repr(e)
# 指数退避 + 抖动: 1s, 2s, 4s 附近随机浮动
time.sleep((2 ** attempt) * (0.5 + random.random()))
print(f"[降级] {model} 不可用({last_error}),切换下一档")
raise RuntimeError(f"全部模型失败,最后错误: {last_error}")
if __name__ == "__main__":
print(chat("用一句话解释什么是指数退避"))几个设计要点,也是我要求团队默认遵守的:
- 重试和降级是两层嵌套循环:先在一个模型内做带退避的重试,耗尽后落到下一档模型,而不是所有请求挤在一个模型上反复撞。
- 区分可重试错误:4xx 里的 429 是限流(等一等值得再试),400 这种就别浪费时间了。
- 最终失败必须抛出:不要静默吞掉错误返回空字符串,那会让下游业务在不知情的情况下继续跑,事故范围反而扩大。
四、协作层面的分工建议
小团队人少,更需要边界清楚。我们目前的分工是:
- 管理者/负责人:决定降级链路的模型顺序、审批 Key 的创建与轮换、订阅配额告警。
- 开发:只调用统一的封装函数,不允许在业务代码里私自写
for i in range(10)式的裸重试。 - 值班的人:收到降级告警后,按预案确认是配额问题还是服务问题,再决定是提工单还是手动切换。
这样安排后,接口故障从“群里炸锅”变成了“按流程走一遍”。四十分钟的抖动那次,我们唯一的损失是一位用户在降级模型上拿到了略简略的回答——这比整站报错好太多。
五、建议的落地顺序
- 注册账号,拿到两把不同环境的 Key;
- 跑通上面的示例代码,确认主模型调用成功;
- 故意把模型名改错一个字符,观察降级链路是否按预期切换;
- 把重试参数挪进配置文件,接入告警,写进值班手册。
整个流程一个下午能走完,但换来的是故障时段的心里有底。如果你的团队还没开始,可以从注册开始:https://api.thistoken.ai/register
---
本文的示例只需一个 API Key 就能复现:在 https://api.thistoken.ai/register 注册即用。