首页 / 文章 / 并发与限流:怎么压测、退避重试、避免 429

并发与限流:怎么压测、退避重试、避免 429

用一段 asyncio 脚本分段加压测出安全并发,讲清指数退避加抖动的写法,并把并发数、RPM、TPM 与成本上限放在一起算,减少 429 和超时,适合正在写请求封装的团队。

上限是测出来的

先跑 4 并发、100 个请求,记 p50、p95 和 429 比例。没有 429 就把并发翻倍,出现 429 就退回上一档,来回两次就能找到安全区。这个拐点跟模型、分组、时间段都有关,换模型要重测。

压测分段做:4、8、16、32 各跑一分钟,中间间隔三十秒,让限流窗口滑过去。一口气压到 64,前面几轮的数据会被后面的失败污染。压测期间关掉本机的其他大流量任务,网卡和 CPU 也会变成瓶颈。

别拿别人的数字当自己的上限。同一时刻上游的排队长度不同,凌晨测出来的并发,晚高峰不一定扛得住。

记录每次压测的并发、模型、分组和时间段,做成一张小表。三天后再看,上限在不同时段能差一倍。

最小压测脚本

python
import asyncio
import time
import aiohttp

URL = "https://ai.idcidiot.com/v1/chat/completions"
HEADERS = {"Authorization": "Bearer sk-..."}
PAYLOAD = {
    "model": "glm-5.3-flash",
    "messages": [{"role": "user", "content": "用一句话解释什么是幂等"}],
    "max_tokens": 64,
}

async def one(session, sem, out):
    async with sem:
        t0 = time.perf_counter()
        async with session.post(URL, json=PAYLOAD, headers=HEADERS) as r:
            await r.read()
            out.append((r.status, time.perf_counter() - t0))

async def main(concurrency=8, total=200):
    sem = asyncio.Semaphore(concurrency)
    out = []
    async with aiohttp.ClientSession() as s:
        await asyncio.gather(*(one(s, sem, out) for _ in range(total)))
    codes = {}
    for c, _ in out:
        codes[c] = codes.get(c, 0) + 1
    lat = sorted(t for c, t in out if c == 200)
    print("codes", codes)
    if lat:
        print("p50 %.2fs p95 %.2fs" % (lat[len(lat) // 2], lat[int(len(lat) * 0.95)]))

asyncio.run(main())

把 concurrency 从 4 改到 64 跑几轮,就能画出延迟和错误率随并发变化的曲线。曲线开始变陡的位置,往回收两档就是生产值。

脚本里的 total 要大于并发数的三倍,样本太少时 p95 没有意义。压测用的提示词和线上保持一致,短提示词测出来的上限会偏高。

退避要带抖动

固定间隔重试会把所有客户端在同一秒放出来,429 只会更密。指数退避加随机抖动,再尊重 Retry-After:

python
import random

def backoff(attempt: int, base=1.0, cap=30.0) -> float:
    wait = min(cap, base * 2 ** attempt)
    return wait * (0.5 + random.random())

第三次重试大约是 4 秒乘以 0.5 到 1.5 的随机数。客户端数量多的时候,这个随机区间比任何固定值都管用。

重试还要设总次数和总时长上限。一个请求卡住两分钟再失败,比立刻失败更伤,连接和并发槽都被占着。

并发不等于吞吐

限制通常按 RPM 和 TPM 两条线走。一百个 200 token 的短请求可能没事,十个 20 万 token 的长请求就会撞 TPM。按 token 估算给请求分组,长请求单独限流,短请求走快通道,整体吞吐会高不少。

连接池也要跟着调。默认的连接数往往低于你的并发设置,表现出来的现象是请求排队、超时,而不是 429。

客户端侧的信号量只管得住自己,管不了同一分组里的其他人。上线前和上游确认配额,比反复调自己的参数有效。

成本闸门

并发上去,账单同步上去。glm-5.3-flash 每次 800 输入、300 输出约 0.0003 美元,一万次 2.96 美元;同样的并发换成 claude-sonnet-5(Claude Max 分组输入 2.55、输出 12.75 美元每百万 token),一次就是 0.0059 美元,一万次 58.6 美元。

压测前把预算写成硬上限:请求数 × 单次成本 × 1.3。超过上限就熔断,别等账单出来才发现压测脚本没停。

熔断之后要有降级路径:换便宜模型、返回缓存结果,或者把请求排队等下一轮。没有降级路径的熔断,只是把故障从 429 变成 500。

观测哪几个数

429 比例、p95 延迟、超时率、每千次请求成本。429 超过 1% 说明并发超了;p95 突然翻倍通常是上游拥堵,先降并发再看日志。

这些指标按模型和分组拆开看,混在一起会掩盖问题。做成一张按小时更新的看板,异常通常先出现在 p95,再出现在 429 比例上。模型与分组的价格在 定价页,各型号的上下文上限见 模型总览,压测用的样本可以从 文章列表 里挑现成的提示词。

先把并发设成 4 跑一轮,拿到自己的基线再谈优化。

用 二五云API 跑通这些模型

OpenAI 兼容接口,一个 Key 调用 GPT、Claude、Gemini、DeepSeek、Kimi 等模型,按量计费。

免费注册查看模型价格

相关文章