登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

AI 接口超时后怎么安全重试:请求标识、指数退避与结果去重

来源:17golang原创

时间:2026-08-24 01:35:25 482浏览 收藏

线上 AI 功能最容易出事故的时刻,往往不是模型返回 400,而是客户端等了 30 秒后只收到一个超时。此时服务器可能还在生成,业务却不知道这次请求到底有没有落地。安全做法不是立刻再发一遍,而是给每次业务操作固定请求标识,区分“可以重试”和“必须人工确认”,再用结果键把重复响应收敛掉。

要点速览
  • OpenAI 可用 X-Client-Request-Id 记录客户端请求标识,服务端响应还应保存 x-request-id 便于排查。
  • Gemini 官方建议只对超时、网络异常、408、429 和 5xx 等瞬态问题做有限次数的指数退避,并加入抖动。
  • 重试前先查本地任务表;没有确定结果时用业务幂等键去重,不能把“重复提交”当成成功。

超时不是失败:先把一次调用拆成三种结果

我在一个“根据用户上传内容生成摘要”的接口上遇到过这个问题:网关 30 秒断开连接,后台日志却显示模型调用在第 34 秒返回成功。前端如果把超时直接当失败,再次点击就会产生两份摘要,甚至重复扣费。

因此,调用记录至少要有 acceptedsucceededfailed 三类最终状态;客户端超时只能写成 unknownunknown 的意思是“结果尚未确认”,不是“可以马上重做”。

触发器、权限和请求标识要在入口固定

把重试逻辑散落在控制器、队列消费者和 SDK 里,最后很难知道到底重发了几次。更稳的入口是先创建一条业务任务,再让所有下游调用继承同一个 job_id

job_id = "summary_20260824_01HZX..."
client_request_id = "job_id/attempt-1"

headers = {
    "X-Client-Request-Id": client_request_id,
    "Idempotency-Key": job_id,
}

X-Client-Request-Id 用来关联一次网络请求,Idempotency-Key 则是应用自己的业务去重键。两者不要混成一个字段:一次任务可能有多次 attempt,但同一个 job_id 仍然只能产生一个业务结果。

流水线按“记录—调用—核对—重试”推进

每个阶段都要有可观察的产物,否则失败后只能凭猜测补请求。

阶段必须记录下一步
创建任务job_id、输入摘要、业务状态进入 queued
发起调用attempt、client_request_id、开始时间等待响应
收到响应HTTP 状态、x-request-id、模型结果写入 succeeded 或 failed
客户端超时超时原因、attempt、最后已知状态先查任务,再决定重试

OpenAI 的响应头包含服务端生成的 x-request-id,排查时要和自己的客户端标识一起保存。Gemini 的官方排障建议则明确把指数退避、随机抖动和最大重试次数放在调用方控制之下。

只对瞬态错误重试,退避时间不要写死

一个够用的策略是:第 1 次等待 1 秒,第 2 次等待 2 秒,第 3 次等待 4 秒,每次加一个 0 到 300 毫秒的随机抖动,并把总等待预算封顶。这样不会让一批同时超时的请求在同一秒再次撞向接口。

retryable = status in {408, 429, 500, 502, 503, 504} or network_timeout
delay = min(30, 2 ** attempt) + random.uniform(0, 0.3)

400 参数错误、401/403 鉴权问题、内容被拒绝以及明确的配额耗尽,不属于“多等一会儿就会好”的瞬态故障。对这些错误重试只会放大日志和成本。对于 504 这类没有响应体的情况,也不要假设服务端没有收到请求,应该先查 job_id 和请求标识。

门禁规则:重试前先查结果,成功后拒绝第二份

重试消费者取到 unknown 任务时,先按 job_id 查结果表,再按请求标识查调用日志。若上游已经成功,就把本地任务补成 succeeded;只有确认没有结果,且错误属于可重试集合,才允许发起下一次 attempt。

if task.result_exists:
    return task.result
if task.attempts >= 3 or task.total_wait_ms >= 30000:
    return mark_manual_review(task)
if task.last_error in RETRYABLE:
    return enqueue_retry(task, next_attempt=task.attempts + 1)
return mark_failed(task)

写结果时再加一道数据库唯一约束,例如 UNIQUE(job_id)。应用层判断能减少重复工作,唯一约束负责挡住并发竞态;两道门都需要,不能只依赖其中一层。

失败处理要能回放,也要能停止

给每个任务保留输入摘要、模型名、attempt 列表和最后一次错误,运维才能回答“这次是否真正发到上游”。当同一任务连续三次超时,或者总等待超过 30 秒,进入 manual_review 比无限重试更安全。人工复核可以重新放行,但要沿用原来的 job_id,并生成新的 attempt 记录。

通知内容也别只写“AI 调用失败”。至少带上 job_id、最后的 HTTP 状态、重试次数、x-request-id(如果有)和用户是否已经看到结果。这样值班同学可以从一条通知跳到完整调用链。

相关问题

超时后立刻重试会不会丢结果?

不会因为“等待”本身丢结果,但可能制造重复任务。先把原任务标成 unknown,查询结果或日志,再决定是否重试。

客户端请求标识能代替幂等键吗?

不能完全代替。请求标识适合排查一次 HTTP 调用,幂等键要绑定业务动作,并由自己的任务表和唯一约束保证结果只落一次。

429 和 500 都应该无限重试吗?

都不应该。根据 Retry-After、指数退避和总预算有限重试;超过预算就停止并告警。

把一次重试变成可核对的结果

可靠的 AI 调用流程不是“失败就再试一次”,而是让每一步都能被还原:谁创建了任务、发了第几次请求、上游给了什么请求标识、结果是否已经落库、为什么停止。把这些证据写进任务表和日志后,超时只是一个待核对状态,重试才会成为可控的业务动作。

AI 接口超时后的请求标识、任务状态与结果核对工程证据场景

AI API 重试门禁从 unknown 查询结果到有限退避和人工复核的流程

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>