收到 429 后,最危险的处理不是“什么都不做”,而是立即并发重试。原请求还没释放占用,新请求又进入队列,短暂限流就可能被放大成持续拥塞。
这份清单把 429 拆成四件可以验证的事:记录在途并发与请求速率、限制应用侧并发、只做有上限的退避重试、队列满时明确降级。全程不需要保存 API Key 或用户原文。
先确认目标
本文解决什么问题?
这篇文章解决“DeepSeek API 返回 429,但不知道是账号并发、请求速率还是重试风暴”的问题。最终产物是一份排障 CSV、一个可本地运行的退避函数和一张 15 分钟处置清单。
这是本地模拟演练,不是生产压测。不要为了验证限流而向供应商批量发送付费请求;实施前还要重新核对当前账号配额和官方规则。
演示任务与可复现输入
假设一个内容摘要接口偶尔返回 429。先不用真实 API,把上游响应固定为下面这组序列:
第 1 次调用:429
第 2 次调用:429
第 3 次调用:200
验收目标也固定下来:最多调用 3 次;两次等待分别为 600ms 和 1100ms;拿到 200 后立即停止;不得出现第 4 次调用。
2026-07-31 复核到的 DeepSeek 官方限速页显示,deepseek-v4-pro 和 deepseek-v4-flash 的账号级并发上限分别为 500 和 2500。一个请求从发出到模型响应完成都算在途并发,限制按账号而不是单个 API Key 计算。官方错误码页则把 429 描述为请求速率达到上限。两页关注的指标不同,因此排障时要把并发与请求速率都记录,不能只猜一个原因。
先判断哪些错误可以重试
| 状态 | 第一动作 | 是否自动重试 | 原因 |
|---|---|---|---|
| 400 | 修正请求体 | 否 | 同样参数再次发送仍会失败 |
| 401 | 检查服务端密钥配置 | 否 | 重试不能修复鉴权 |
| 402 | 检查余额与告警 | 否 | 应先停止新增付费任务 |
| 422 | 修正参数 | 否 | 属于请求合同问题 |
| 429 | 降低并发、排队、退避 | 有上限地重试 | 立即并发重试会继续占用容量 |
| 500 | 保留请求标识后等待 | 有上限地重试 | 可能是短暂服务故障 |
| 503 | 降级或等待 | 有上限地重试 | 上游当前繁忙 |
不要通过创建多个 API Key 绕过账号限制。DeepSeek 官方说明并发按账号粒度计算;普通账号传入不同 user_id 时,总并发仍合并计算。若使用 user_id 做调度隔离,也不要放入邮箱、手机号或其他隐私信息。
可复制排障日志
在原有 7 天 Token 用量日志 旁边增加一张短期故障表。表头只保留定位限流需要的字段:
timestamp,request_id_hash,model,status_code,in_flight,requests_60s,attempt,delay_ms,outcome
本地模拟可以得到下面三行:
2026-07-31T20:00:00+08:00,0c71a3f2,deepseek-v4-flash,429,12,84,1,600,retry_scheduled
2026-07-31T20:00:00.600+08:00,0c71a3f2,deepseek-v4-flash,429,9,85,2,1100,retry_scheduled
2026-07-31T20:00:01.700+08:00,0c71a3f2,deepseek-v4-flash,200,6,86,3,0,succeeded
in_flight 是当前未完成请求数,requests_60s 是滚动 60 秒内发起的请求数。两者要同时记录,因为官方限速页与错误码页分别从并发和请求速率描述 429。request_id_hash 只用于串起同一业务任务的重试,不应包含用户身份或提示词。
操作过程:15 分钟定位 429
第 0—2 分钟:先停止重试扩散
暂停无上限重试和批处理入口,保留已经在途的请求。不要重启服务后立刻把积压任务全部重新发送。
第 2—5 分钟:看账号总并发
汇总所有实例和所有 API Key 的
in_flight。若只看单台机器,会漏掉其他实例对同一账号配额的占用。第 5—10 分钟:对照请求速率与队列
比较 429 出现前后的
requests_60s、队列深度和响应时长。响应越慢,请求占用并发的时间越长,同样流量也可能堆出更高在途数。第 10—15 分钟:选择限流或降级
给应用设置低于账号上限的并发闸门、有限队列和最大等待时间。队列满时返回可读提示;备用供应商只有在价格、隐私和功能都预先验收后才能启用。
可复制的有界退避代码
下面的 TypeScript 只处理已经收到 HTTP 响应的 429、500 和 503。它把等待函数和随机数注入参数,因此可以在本地无等待地测试。
type Send = () => Promise<Response>
type Sleep = (delayMs: number) => Promise<void>
const RETRYABLE_STATUS = new Set([429, 500, 503])
const MAX_ATTEMPTS = 3
const BASE_DELAY_MS = 500
const MAX_DELAY_MS = 4_000
async function callWithBackoff(
send: Send,
sleep: Sleep = (delayMs) =>
new Promise((resolve) => setTimeout(resolve, delayMs)),
random: () => number = Math.random,
) {
let lastResponse: Response | undefined
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
const response = await send()
if (!RETRYABLE_STATUS.has(response.status)) return response
lastResponse = response
if (attempt === MAX_ATTEMPTS) break
const backoffMs = Math.min(
MAX_DELAY_MS,
BASE_DELAY_MS * 2 ** (attempt - 1),
)
const jitterMs = Math.floor(random() * 250)
await sleep(backoffMs + jitterMs)
}
return lastResponse
}
这段代码没有处理网络超时。超时不代表上游一定没有执行请求;若任务会触发发信、扣款、写数据库等副作用,还需要业务幂等键和总截止时间,不能照搬自动重试。
结果样例与验收记录
用前面的 [429, 429, 200] 序列运行本地模拟:
const statuses = [429, 429, 200]
const delays: number[] = []
let calls = 0
const response = await callWithBackoff(
async () => new Response(null, { status: statuses[calls++] ?? 503 }),
async (delayMs) => {
delays.push(delayMs)
},
() => 0.4,
)
console.assert(response?.status === 200)
console.assert(calls === 3)
console.assert(JSON.stringify(delays) === "[600,1100]")
| 验收项 | 预期结果 | 本地样例 |
|---|---|---|
| 最大尝试次数 | 不超过 3 | 3 |
| 第一次等待 | 500ms 退避 + 100ms 抖动 | 600ms |
| 第二次等待 | 1000ms 退避 + 100ms 抖动 | 1100ms |
| 成功后停止 | 不再调用上游 | 通过 |
| 第三次仍失败 | 返回最后一个响应,由业务降级 | 代码路径已覆盖 |
这只能证明客户端策略符合预期,不能证明真实账号的限额,也不能代替生产监控。真实上线后,至少观察一个完整流量周期,再比较 429 比例、重试放大倍数和队列等待时间。
上线前检查清单
| 检查项 | 通过条件 |
|---|---|
| 账号视角 | 所有实例和 API Key 的在途请求能合并统计 |
| 并发闸门 | 应用并发低于当前账号配额,并留有余量 |
| 队列上限 | 有最大深度和最大等待时间,满载时明确拒绝 |
| 重试上限 | 429、500、503 最多尝试 3 次,不递归无限重试 |
| 抖动 | 并发请求不会在同一毫秒集体重发 |
| 不可重试错误 | 400、401、402、422 直接进入对应处置流程 |
| 日志边界 | 不记录 API Key、完整提示词、回答或用户隐私 |
| 降级边界 | 备用模型、成本与数据规则已提前验收 |
| 复核日期 | 上线当天重新查看官方限速和错误码页面 |
官方资料与事实边界
- DeepSeek 限速与隔离:说明当前模型并发、账号粒度、
user_id隔离和请求保活机制。 - DeepSeek 错误码:区分 400、401、402、422、429、500 与 503 的原因和处理方向。
- DeepSeek 常见问题:说明更高并发需要提交扩容申请,并提供当前账号侧说明。
本文中的 3 次尝试、500ms 基础等待和 250ms 抖动是演示用客户端策略,不是 DeepSeek 官方保证值。模型名、并发数字与扩容政策都可能调整,实施时以账号控制台和官方页面为准。
失败边界与不适合条件
- 429 消失不代表系统健康。队列等待过长、失败转为超时或用户放弃,仍然是故障。
- 降低并发可能减少 429,却会增加排队时间;必须同时看成功率和端到端延迟。
- 自动切换供应商会改变价格、输出质量和数据处理边界,不能作为未经审核的默认动作。
- 同一任务超时后再次发送,可能产生重复成本;没有业务幂等设计时不要假设“没收到响应就没执行”。
- 本文不是容量承诺,也不建议用付费生产接口主动撞限额。
FAQ
多创建几个 API Key 能提高并发吗?
不能据此推断。DeepSeek 当前限速页明确按账号而不是 API Key 计算并发;应该统计账号总量,必要时走官方扩容流程。
收到 429 后要等多久?
先使用有上限的指数退避和抖动,并结合自己的总体超时。本文给出的等待数字只用于本地演练,不是供应商 SLA。
为什么要同时记录在途并发和 60 秒请求数?
因为官方限速页以账号并发解释限制,错误码页又从请求速率描述 429。两项一起记录,才能区分长响应堆积和短时请求突增。
429 为零就可以把并发继续调高吗?
不能只看一天或一个指标。先确认高峰期成功率、P95 等待时间、队列溢出和重试倍数,再小步调整并保留回退值。
阅读结论
总结
处理 DeepSeek API 429 的核心不是“多重试几次”,而是把账号总并发、请求速率、队列与重试放在同一张记录里。先阻止重试风暴,再用有限队列和有界退避恢复,最后用真实日志证明 429、等待时间和重试成本都回到可接受范围。