DeepSeek API 报 429 怎么办:并发限流不是配额用完

2026-07-27

数据截至 2026-07,价格与限额以各官网为准。

DeepSeek API 返回 429 的时候,绝大多数情况不是你的钱花完了,而是同一时刻在途的请求太多,撞上了并发上限。 这两件事的处理方式完全相反:配额问题要去充值或等重置,并发问题只需要在客户端把”同时发出去的请求数”压下来,代码里加十几行就能解决,一分钱不用多花。分不清这两者,就会出现”我明明还有余额,为什么一直 429”的困惑,然后白白去翻账单页面。

先把这个误解掰过来:429 不等于”没额度了”

HTTP 429 的标准语义是 Too Many Requests,字面意思是”请求太多”,不是”你没钱了”。很多人被别的平台的习惯带偏了——有些服务确实会在余额不足时返回 429,但 DeepSeek 官方文档里对限流的表述是并发数deepseek-v4-flash 为 2500 并发,deepseek-v4-pro 为 500 并发。

注意”并发”这个词的准确含义:它指的是同一时刻正在处理中、还没返回结果的请求数量,不是”每分钟能发多少次”。这两个口径差别很大。举个例子,如果每个请求平均耗时 2 秒,你用 500 的并发跑满,一分钟其实能完成大约一万五千次请求;反过来,如果你的请求是长上下文推理、单次要跑三十秒,那 500 并发一分钟也就千次左右的吞吐。所以”我一分钟才发了几百个请求怎么会限流”这种推理是不成立的——决定你会不会撞墙的是在途请求数,不是发送频率。

也正因如此,最容易撞 429 的场景往往不是高流量线上服务,而是本地跑批:写个脚本把两千条数据一股脑丢进线程池,前几十个还好好的,剩下的全在报错。线上服务反而因为请求是零散到达、平均在途数量可控,很少撞到。

另外要说明的是,除并发之外官方是否还有其他维度的限制(比如按分钟计的请求数或 token 数),这篇不替官方下结论,以你调用时 api-docs.deepseek.com 上的当前页面为准。这篇只依据官方明确标注过的并发口径来给方案。

第一步:先确认是不是真的限流

在动手改代码之前,花两分钟排除掉几种”长得像 429,其实不是限流”的情况,否则你限了半天并发问题依然在。

  • 看响应体,别只看状态码。HTTP 客户端库经常只把 status code 抛出来,真正的错误描述在 body 里。用 curl -i 或者在代码里把原始响应打印出来,看清楚服务端到底说了什么,是并发超限、鉴权问题还是余额问题。
  • 确认请求真的打到了 DeepSeek。base_url 应该是 https://api.deepseek.com。如果你是从别的兼容服务改过来的,或者中间挂了代理、网关,429 有可能是中间那一层限你的,跟 DeepSeek 一点关系没有。这种情况在公司内部统一网关的场景里非常常见,排查时先绕过网关直连试一次,结论立刻就清楚了。
  • 确认不是模型名的问题。旧的 deepseek-chat / deepseek-reasoner 两个名字官方标注在 2026 年 7 月 24 日 15:59 UTC 弃用,新代码请直接写 deepseek-v4-flashdeepseek-v4-pro。模型名不对通常报的是别的错,但排查期一次性把它核对掉,能省掉后面反复怀疑的时间。这块细节可以看 deepseek-chat / deepseek-reasoner 报 404 了?
  • 确认密钥没有被别人共用。并发是按账号算的,不是按你这台机器算的。如果同一把 key 同时被测试环境、同事的脚本、某个忘了关的定时任务在用,你自己这边看着只发了几十个请求,账号层面早就满了。密钥在 https://platform.deepseek.com/api_keys 管理,建议按用途拆成多把,出问题时能定位到具体是谁在占。

第二步:用信号量把在途请求数摁住

确认是并发限流之后,正解不是”重试到成功为止”,而是从源头上不让在途请求数超过阈值。重试是兜底,限流才是主动控制。

Python 里最简单的做法是用信号量。同步版本配合线程池:

import os
import threading
from concurrent.futures import ThreadPoolExecutor
from openai import OpenAI

client = OpenAI(
    api_key=os.environ.get("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com",
)

# 这个数字自己定,务必留出余量,不要贴着官方上限跑
MAX_INFLIGHT = 50
sem = threading.Semaphore(MAX_INFLIGHT)

def ask(prompt: str) -> str:
    with sem:  # 拿不到令牌就在这里排队,而不是把请求打出去
        resp = client.chat.completions.create(
            model="deepseek-v4-flash",
            messages=[{"role": "user", "content": prompt}],
        )
        return resp.choices[0].message.content

with ThreadPoolExecutor(max_workers=MAX_INFLIGHT) as pool:
    results = list(pool.map(ask, prompts))

异步版本同理,把 threading.Semaphore 换成 asyncio.Semaphorewith 换成 async with 就行。asyncio 场景下更要留意这一点:协程创建成本极低,一个 asyncio.gather 轻轻松松就能同时发出去几千个请求,没有信号量的话撞墙几乎是必然的。

MAX_INFLIGHT 该设多少?给一个务实的思路:从小往大调,而不是从官方上限往下退。先设个几十跑通,观察实际吞吐够不够用,不够再往上加。理由有三个:一是你的密钥可能被多处共用,官方上限不等于你一个人能用的量;二是本机的网络连接数、内存、下游数据库往往比 API 先成为瓶颈;三是贴着上限跑意味着任何抖动都会立刻变成错误。留出余量的代价只是吞吐略低,撞墙的代价是整批任务需要重跑。

第三步:退避重试怎么写才不添乱

限住并发之后仍然可能偶发 429,这时候需要重试兜底。但重试写不好会放大问题——最典型的反面教材是”失败了立刻重发”,一批请求同时失败又同时重发,等于把冲击又完整地打了一遍。

正确的姿势是指数退避加随机抖动

import random
import time

def ask_with_retry(prompt: str, max_retries: int = 5) -> str:
    for attempt in range(max_retries):
        try:
            return ask(prompt)
        except Exception as e:
            if not _is_rate_limited(e) or attempt == max_retries - 1:
                raise
            # 1s、2s、4s、8s… 再叠加随机抖动,避免所有请求同时回来
            delay = (2 ** attempt) + random.uniform(0, 1)
            time.sleep(delay)

几点说明:

  • 退避的基数和上限是经验值,不是官方规定,上面写 1 秒起步、每次翻倍纯粹是常见做法,你可以按自己的场景调。如果响应头里带有服务端给出的等待时长提示,优先按它来,具体有没有这个字段、叫什么名字以官方文档当次页面为准,别照抄别的平台的字段名。
  • 随机抖动那一项不要省。同一批同时失败的请求如果退避时长完全一致,它们会在同一时刻再次涌回来,等于什么都没改善。
  • 只对限流类错误重试。参数错误、模型名不存在、鉴权失败这些重试一百次也是同样的结果,只会浪费时间。上面代码里的 _is_rate_limited 需要你按实际拿到的异常类型和响应内容去判断,不要无脑 except 全部重试。
  • 重试次数要有上限。无限重试会让一个本该快速失败的任务挂在那里,掩盖真正的问题。

别忽略缓存:它能顺带缓解压力

DeepSeek 有一个叫上下文硬盘缓存的机制,官方文档标注默认对所有用户开启,不需要改代码。它的直接价值是省钱:以官方人民币定价看,deepseek-v4-flash 缓存命中的输入价是 0.02 元/百万 tokens,未命中是 1 元/百万 tokens;deepseek-v4-pro 分别是 0.025 元和 3 元/百万 tokens。差距是数十倍量级。

跟 429 的关系在于:命中缓存的请求,模型不需要重新处理那部分前缀,单次请求的处理时间通常会更短,在途时间短了,同样的并发额度下能过的请求自然更多。所以把提示词组织成”固定前缀在前、变化内容在后”的结构,既省钱也间接缓解并发压力。

要注意几个前提,别抱不切实际的期待:

  • 缓存只匹配输入的前缀部分,输出仍然要模型实际生成,不受缓存影响,随机性也不变。
  • 缓存是尽力而为的,官方明确不保证 100% 命中。
  • 不使用的缓存会自动清理,官方原文的说法是”一般数小时至数天”。
  • 想确认到底命中没有,看返回 usage 里的 prompt_cache_hit_tokensprompt_cache_miss_tokens 两个字段,别靠感觉判断。

关于计费的完整说明,可以看 DeepSeek API 怎么收费。另外网上有关于峰谷时段差异定价的说法,这篇不采信未在官方定价页正式公示的数字,以你查看时 api-docs.deepseek.com 和 platform.deepseek.com 页面显示的为准。

还有哪些减压手段

除了限并发和用缓存,下面这些也值得考虑,按投入产出排序:

  • 该用 Flash 就别上 Pro。官方标注的并发额度本身就不同,Flash 是 2500、Pro 是 500,差了五倍。分类、抽取、格式转换这类任务用 Flash 通常够用,把 Pro 留给真正需要深度推理的环节,等于凭空多出好几倍的并发空间,顺带还省了钱。
  • 合并请求。一次调用里处理十条数据,比拆成十次调用更省并发额度。当然要权衡:合并后单次输出变长、在途时间也变长,而且一条出错可能拖累整批,需要按实际数据量试。
  • 在队列层削峰。如果业务本身是异步可延迟的,把任务放进队列由固定数量的 worker 消费,是比在调用点做信号量更结构化的做法,也更容易观测和调整。
  • 加监控。至少记录三个数字:当前在途请求数、429 出现次数、平均单次耗时。没有这三个数,调并发参数就是纯猜。

什么情况下这些办法不管用

诚实说几个不适用的场景,免得你在错误的方向上耗时间。

如果你的 429 是在第一个请求就出现的,那基本可以排除并发问题,去查密钥状态、账户余额和请求本身。如果是通过第三方中转或聚合平台调用的,那么限流规则由那个平台自己定,跟 DeepSeek 官方标注的并发数没有关系,这时候官方文档帮不了你,得看你用的平台自己的说明——相关取舍可以看 API 聚合与中转平台怎么选。如果你限并发之后吞吐达不到业务要求,那问题的性质已经变了,不再是”怎么绕开 429”,而是”单请求耗时能不能优化”或”要不要拆多账号、走更高档位”,后者需要跟官方渠道确认政策,不是客户端代码能解决的。

还有一种情况:你的请求本身就是超长上下文。官方标注 V4 系列上下文窗口 1M tokens、最大输出 384K tokens,能力上撑得住,但这类请求单次在途时间长,会长时间占着并发名额。如果它们和大量短请求混在一起跑,短请求会被拖累。做法是把长短请求分开跑,各自设不同的并发上限,别放在同一个池子里抢。

小结

DeepSeek 的 429 绝大多数是并发限流,跟余额没关系,官方标注的口径是同时在途请求数:Flash 2500、Pro 500。先用 curl -i 看清响应体、排除网关和共用密钥的干扰,确认确实是限流再动手。核心解法是客户端信号量,从几十开始往上调,别贴着官方上限跑。退避重试只做兜底,必须带随机抖动、必须只对限流错误重试、必须有次数上限。顺手把提示词的固定前缀提到最前面吃上缓存,既省钱也缩短在途时间。真到了限并发也满足不了吞吐的地步,那就是架构问题而不是报错问题了,得换个思路解。

接下来看什么

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。