周五上线前,我在北京值班室给并发请求设上限
北京值班室里的真实问题:并行不等于越多越快
周五上线前,我们要让一批用户请求同时调用模型。最初的想法很直接:每个任务启动一个请求,任务越多,处理速度越快。结果一到流量高峰,请求开始排队,部分请求超时,重试又制造了更多请求,最终整体速度反而下降。
这里的关键概念就是并发限制。它规定同一时间最多允许多少个请求处于执行状态。并发数为十,意味着最多十个请求正在等待响应;第十一个请求不会立即发出,而是等待前面的请求完成、失败或超时。
先区分并发、速率和总任务数
这三个数字经常被混在一起。并发是“同时进行多少个”,速率是“每秒发出多少个”,总任务数是“这批工作一共有多少个”。例如,一次有一千个任务,可以限制并发为二十,并把速率限制为每秒五个。这样既不会瞬间冲击服务,也不会让客户端创建一千个活动连接。
并发限制主要保护三个地方:你的应用进程、网络连接和上游 API。AI 请求通常持续时间较长,还可能返回较大的内容,因此十个并发请求消耗的资源,不能简单按十个普通网页请求来估算。
用固定容量的信号量控制入口
最容易理解的实现方式是信号量。它像一组有限的通行证:启动请求前领取一张,请求结束后归还。通行证用完时,新任务进入等待队列,而不是继续创建连接。
- 把并发上限设置为一个明确的整数,例如八或十六。
- 请求开始前获取许可,成功、失败和取消时都释放许可。
- 释放许可的操作放在统一的清理逻辑中,避免异常导致许可永久丢失。
- 等待许可也要有超时时间,不能让任务无限期停留在队列里。
实践中不要为每个任务都创建独立线程。使用已有的异步任务池、工作线程池或队列消费者,通常更容易观察和停止。并发上限应该是配置项,而不是散落在代码中的常量。
根据请求类型设置不同的并发预算
所有模型请求不一定应该共享同一个上限。短文本分类和长文本生成的响应时间、令牌消耗和失败概率都不同。可以先给交互请求八个并发名额,再给后台批处理四个名额,避免批处理把用户请求全部挤出队列。
如果应用通过 OpenAI 兼容接口访问多个模型,还要考虑上游账户的限制。比如使用 Claude Sonnet 5.5 处理长内容时,可以设置较低的并发;使用 DeepSeek V4 Flash 处理短任务时,可以设置另一套预算。不要只看模型名称猜性能,应该用真实请求记录测量响应时间、错误率和令牌用量。
超时、取消和重试必须一起设计
并发限制只能控制入口,不能解决永远不返回的请求。每个请求都应有连接超时、读取超时和整体截止时间。整体截止时间到了,就取消请求并释放并发许可。
重试也必须受到限制。网络暂时失败时,可以使用带随机抖动的指数退避,例如等待一秒、两秒、四秒,但最多重试两到三次。对于认证失败、参数错误和明确的配额不足,不要盲目重试。否则一次故障会被放大成多轮请求。
让队列有边界,明确拒绝过载
无限队列看起来友好,实际上会把问题推迟到内存和用户体验上。给队列设置最大长度,例如一百个任务。队列已满时,应立即返回“系统繁忙,请稍后重试”,或者把任务持久化到可靠的后台队列。
队列中的任务还需要公平规则。交互请求通常优先于低优先级批处理;等待太久的任务可以提升优先级,避免一直得不到执行。记录任务进入队列的时间,才能判断用户是在等待模型响应,还是在等待你的并发许可。
用指标验证上限是否合适
至少记录当前活动请求数、队列长度、排队时间、请求耗时、超时数量、各类错误和重试次数。上线前先从较低并发开始,逐步增加,观察成功率是否下降、平均耗时是否突然上升。
如果并发从八提高到十六后,吞吐量几乎不变,但超时明显增加,说明瓶颈在上游或网络,而不是客户端不够积极。此时继续提高并发没有价值。可以为活动请求数和队列长度设置告警,让值班人员在用户大量失败前看到趋势。
把 RelayVano 放进可控的调用层
如果你需要通过统一的 OpenAI 兼容接口调用 AI 模型,可以考虑 RelayVano。它按量计费,API base 是 https://api.relayvano.com,适合在 Claude Code、Codex、Gemini CLI 或自建 SDK 调用层前面加上自己的队列、超时和并发策略。无论请求进入哪个模型,并发控制都应放在你的应用侧,不能假设上游会替你完成业务级排队。
上线前用一小批真实负载压测:先设定并发八,观察五分钟,再逐步调整;确认成功率、排队时间和费用都可接受后,再扩大范围。准备使用这套按量调用方式时,可以注册 RelayVano 账户,先用低并发验证配置,再根据指标增加预算。