你拿到一个并发限制为 1 的 Key,或者中转站给你的档位只允许同时跑一个请求。这时候最亏的不是慢,而是脚本一并发就一堆失败、报错重试又把额度烧掉、跑到一半断了只能从头再来。这篇不讲原理,直接给一套单槽位排队方案:怎么串起来、怎么记录进度、失败怎么退、什么情况下这套做法不划算。
你拿到一个并发限制为 1 的 Key,或者中转站给你的档位只允许同时跑一个请求。这时候最亏的不是慢,而是脚本一并发就一堆失败、报错重试又把额度烧掉、跑到一半断了只能从头再来。这篇不讲原理,直接给一套单槽位排队方案:怎么串起来、怎么记录进度、失败怎么退、什么情况下这套做法不划算。
先明确一个前提:单并发档位下,同时发第二个请求通常不是「排队等一下」,而是直接判失败。所以你要做的是从设计上就把并发数当成 1,而不是指望偶尔并发一下没事。具体这条链路到底允许多少、超了怎么报错,到 /sites 看对应站点的口径说明再动手。
先接受串行:把并发数当成 1 来设计
最省事的做法是把默认并发写死成 1,请求之间用 await 或者一个显式队列串起来。别在本地开多线程、多进程去「加速」,开了只会制造限流,把失败重试的额度算进去反而更贵。纯串行任务比如翻译一批段落、批量改写,单并发照样能跑完,只是线性耗时。
- 适合串行
- 批量翻译、批量改写、离线生成这类不需要人等的任务,慢一点但稳
- 不适合串行
- 用户在页面上等你出结果的交互式场景,排队意味着响应时间成倍放大
- 想压时间
- 走多 Key 轮换或换更高并发档位,不要在同一条链路上硬并发
做一个落盘的队列和进度表
核心思路是:任务先落成一份待办清单,执行器按行取、按行写回,进程被 kill 也能接着跑。状态至少分三档——待处理、成功、失败待重试;成功的一行不再重复投递,失败的行要带上尝试次数,方便后面判断是继续重试还是放弃。
| 字段 | 写什么 | 为什么重要 |
|---|---|---|
| id | 每行唯一标识 | 断点续跑时用它去重,避免重复烧额度 |
| input | 这条任务的输入内容 | 重跑时能对得上,输入快照要一起存 |
| status | 待处理 / 成功 / 失败待重试 | 决定执行器下一次要不要再取这一行 |
| attempts | 已尝试次数 | 区分偶发失败和顽固失败,控制重试上限 |
- 小团队用一份 JSONL 文件加一个 while 循环就够,不必上任务队列中间件
- 任务量到几万条、要多人同时看进度时再考虑数据库
- 输出先追加写文件,每条结果带 id,最后再按 id 合并
- 合并阶段用 id 去重,能挡住「其实成功了但你没收到」的重复计费
控制节奏:间隔、重试与「什么情况该停」
两次请求之间留一个固定间隔,比「能多快就多快」更稳。这个间隔没有通用值,取决于你这条链路的基线延迟,先去 /tools/speed-test 量出当时的表现,再加一点缓冲当间隔。盲抄别人的数字,很可能在你这里还是撞限流。
- 分类失败原因网络超时、网关类错误可以重试;鉴权失败、参数错误、余额不足这类重试没意义,直接标记失败并停下来看
- 逐次拉长间隔同一个失败任务重试 2-3 次,每次把等待间隔拉长,给对端一个缓冲窗口
- 设批次止损连续失败多少条、总耗时超多久就停,别让脚本自己跑到天亮把额度耗光
- 保证幂等重试前确认这条请求重复发不会产生额外副作用,并记录每次尝试的序号
这套方案什么时候不管用
第一种情况是实时交互:用户在页面等你出结果,串行排队会把响应时间成倍放大,这时候应该换档位或换供应商,而不是硬扛。第二种是任务之间有强依赖顺序、必须边跑边看结果的,落盘队列反而增加复杂度,直接串行调用、不落盘更省事。
- 输入每次都不一样(比如实时抓取)时,必须把输入快照一起存下来,否则重跑结果对不上
- 并发限制随时可能被平台调整,去 /sites 看该站的口径说明,别把一次测出来的限制当长期契约
- 任务规模很小、几十条以内时,落盘队列的搭建成本可能高于收益,直接串行循环更快
- 要横向扩的时候,先确认多 Key 轮换是否被允许,别自己拼出一条会被判异常的链路
常见问题
并发 1 是不是就没法提速了?
单条链路确实只能串行,但可以缩短单次耗时:把输入裁剪到必要长度、能不用大模型的地方用规则处理、开流式只是改善体感不减少总时长。想真正并行只有两条路:拿到多个 Key 分桶跑,或者升级到更高并发档位。
重试会把额度浪费掉吗?
会。失败请求很多平台照样计费或计次数,所以重试要分类型:可重试的错误限次数并拉长间隔,不可重试的错误立刻停。同时记录每次尝试,避免重复投递同一个任务。具体计费口径去 /official-api 对照你用的那家。
跑一半断了,怎么接着跑?
前提是任务和结果都落盘、每条带唯一 id。重启时读已有输出集合,跳过已成功的 id,只重跑待处理和失败待重试的行。做这一步的成本很低,但能省下整批重跑的时间。