调接口时经常遇到两种「被挡住」:一种是你压根没超并发数却收到 429,另一种是并发拉太高把本地连接池和队列撑爆。很多人把这两件事混着看,改参数改了半天没改到点上。这篇只做一件事:把限流规则和并发控制拆成两条线,告诉你各自管什么、怎么判断当前卡在哪一层、以及先动哪个旋钮。
调接口的时候,卡壳的情况其实分两种:一种是你压根没开几个线程,却被服务端直接打回一句「请求太多」;另一种是线程拉得很猛,服务端没说啥,结果本地连接池先炸了。很多人把这两件事当成一回事,参数来回改,改完发现没用,因为压根没改到点上。
这篇只做一件事:把限流和并发控制拆成两条线。各自管什么、怎么判断当前卡在哪一层、先动哪个旋钮,讲清楚就够了。
先分清:一个管「你被允许多快」,一个管「你同时压多少」
限流是服务端或者网关下发的准入规则,决定的是单位时间内你能发多少次请求,超了就直接拒。这个额度窗口是对方定的,你改不了,只能顺着它调自己的节奏。并发控制则完全是你这一侧的事:同时有多少个请求在外面飞,是你本地代码里一个可以随手改的旋钮。
两者容易互相伪装。并发拉高会让单位时间的请求数变多,于是撞上服务端的限流;反过来限流拒掉一部分请求,你的实际并发数会看起来虚高或者虚低,监控图上的数字就不准了。判断顺序上,先看响应里服务端给的限流信号(状态码、重试等待提示、剩余额度头),再看自己这边的并发计数和排队长度。
- 限流
- 服务端或网关下发的准入规则,超出单位时间额度就拒绝,客户端无法修改
- 并发控制
- 客户端自己决定同时飞出去多少请求,是本地可调参数
- 伪装效应
- 并发高会推高单位时间请求数从而触发限流;限流又会让你看到的实际并发失真
怎么判断你这次卡在哪一层
| 观察项 | 像限流的样子 | 像自己并发问题的样子 |
|---|---|---|
| 返回速度 | 请求一发出就快速失败 | 发出去了,慢慢耗到超时 |
| 失败分布 | 某一小段时间窗口内成批出现 | 持续性的长尾延迟、连接超时 |
| 重试效果 | 重试也不好,等待后才有改善 | 重试偶尔能成,但整体越来越慢 |
| 本地指标 | 连接池水位正常 | 连接池占满、队列长度一直涨 |
- 看请求的时间线:被限流的请求耗时通常极短;被自己并发拖住的请求是缓缓耗到超时。
- 看失败是否成批:限流失败往往集中在某个时间窗内扎堆出现;并发问题表现为长期的长尾。
- 看本地资源:连接池占用、排队深度、活跃连接数,这几个指标涨了就是自己这边的事。
- 先把中间层的自动重试关掉再测,否则网关、中转层各自重试叠加,两层混在一起根本分不清是哪一层的锅。
动手调的顺序:先治限流,再调并发
- 对着服务端的等待提示做退避别用固定间隔硬重试,按对方给的等待时间退避,并加随机抖动,避免多个实例同一秒一起冲上去。验证方式:观察被拒次数是否明显下降。
- 把并发拆开设请求发起并发和连接池上限分开配,池子比并发数略大即可,否则请求会在本地排队等着拿连接。验证方式:连接池等待时间应该接近零。
- 排队和分批,而不是直接丢超出额度的部分先入队,配一个明确的失败重投上限,避免无限重试把额度烧光。验证方式:被丢弃的请求数应可控。
- 加观测再看调参有没有用每分钟记录成功数、被拒数、平均耗时和排队深度。只有这几个数字同时看,才知道调整是往好的方向走还是把问题挪了个位置。
有一点要提醒:交互式场景不合适长排队。用户就在那儿等着看结果,排队深度一高就该快速失败并给提示,而不是让人干等。批量任务、离线补数据这类场景才适合把队列堆高一点。
把这件事固定成一条可复用的清单
- 上线前先量基线:单独发请求测一遍响应延迟,到 /tools/speed-test 看看正常时这条链路是什么水平。
把限流相关字段和并发计数一起打日志,事后复盘时能一眼看出是外部拒绝还是内部排队。给每个上游单独设并发上限,不要所有接口共用一个全局并发数,一个慢接口会把额度全占住。
| 环节 | 要落到日志里的字段 | 用来判断什么 |
|---|---|---|
| 请求发出 | 时间戳、目标上游、当前并发数 | 并发是否被拉高 |
| 响应返回 | 状态码、耗时、服务端限流头 | 是被拒还是自己慢 |
| 本地资源 | 连接池水位、队列深度 | 是不是自己这侧堵了 |
| 定期核对 | 上游额度口径、模型规格 | 配额是否已经变了 |
常见问题
我没超并发数,为什么还是被限流?
因为限流和并发数的计量口径常常不同:限流可能按单位时间请求数、按 token 量或按账号共享额度来算,你的并发数只是一个瞬时快照。多个进程共用一个 Key、或者有重试在放大请求量,都会让你「看起来没超并发」但实际已撞线。先看服务端返回的限流信号,再统计整个账号层面的请求速率。
并发数应该设多少?
没有通用答案,取决于上游额度、你的超时设置和单个请求耗时。稳妥做法是从小往大试:先设一个明显保守的值,加压观察成功率和平均耗时,逐步加上去,直到被拒率或超时开始上升就退一格。同时给连接池留点余量,池子小于并发数时请求会在本地排队,看起来像限流其实是自己堵自己。
限流和并发控制能不能只做一层?
通常两层都要,但职责不同。服务端的限流你改不了,只能通过退避、分批、排队去适应;客户端的并发控制是你唯一能主动设计的部分,它决定你会不会把上游打爆、以及失败时表现得多难看。只做退避不做并发收敛,高峰期请求会堆成雪崩;只限并发不处理限流信号,遇到额度收紧照样全量失败。