concurrency limit 1怎么用函数表示

接入与开发

给写代码的人讲清怎么用函数把并发数锁成 1:串行队列、信号量、Promise 链三种写法的取舍与排查顺序。

线上接口一并发就报限流,你最省事的做法往往不是加机器,而是先把调用改成一次只发一个。可真动手时问题来了:用一个函数怎么表达「同时只能有一个在跑」?这篇按语言和场景拆开讲,给出几种能直接抄的写法、各自适用的边界,以及写完之后怎么验证它真的串行。

线上接口一并发就被下游限流,很多人的第一反应是加机器或者换服务,但其实最省事的做法往往是把调用改成一次只发一个。问题在于,怎么用函数把这个「同时只能有一个在跑」表达出来?不同语言、不同场景下写法差别不小,写歪了还容易把队列卡死。

这篇按串行队列、信号量降级、Promise 链三种思路拆开讲,给出可抄的结构、各自适用的边界,以及写完之后怎么验证它真的做到了串行。看完你应该能对着自己的代码选出一种,并且知道要防哪几个坑。

什么情况下要把并发压到 1

先把判断标准说清楚:要不要把并发压到 1,不取决于「请求多不多」,而取决于「共享的那份资源是不是只有一个」。共享的是单个账号的配额、一条长连接、一个文件句柄,还是数据库里同一行记录,这几种情况的性质并不一样,但都可以归结为同一份资源得排队用。

  • 单账号配额只允许一个在途请求,多开一个就直接报错或触发风控
  • 下游是单线程服务,或者对同一连接的并发使用本身就不安全
  • 多个任务要写同一个文件、同一张表或同一行记录,互相会踩
  • 调试阶段想先排除并发带来的随机性,先把执行顺序固定下来

反过来说,如果任务之间完全独立、下游也扛得住并发,那串行只是白白把总耗时拉长,没有任何好处。这种情况下应该去提升并发而不是压制它,可以先到 /tools/speed-test 测出单次调用的实际延迟,再决定串行之后的总时长你能不能接受。

三种常见写法:队列、信号量、Promise 链

显式队列是最土也最好调试的一种:维护一个任务数组和一个「正在跑」的标志,任务进来就入队,如果当前没有在跑的就立刻执行,执行完再取出下一个。任何语言都能这么写,出问题时打日志一看就知道卡在哪一步。

信号量降级则是少写代码的路子。很多语言或框架自带信号量、线程池或并发控制工具,把并发上限参数设成 1 就完事。但要先确认它的默认行为是排队等待还是直接拒绝——有的实现满了就抛错,那就不是你要的「锁成 1」。

Promise 链适合脚本和一次性批处理:把异步调用一个个挂上去,后一个等前一个 resolve 之后再发。缺点是任务数量不确定时不方便中途插入,长期驻留的服务用这种写法容易越挂越长。

写法适合的场景主要代价
显式队列任务持续到来、需要排队和观测的服务要自己处理异常释放和队列上限
信号量降级框架已有并发工具、想少写代码要确认默认是排队还是直接拒绝
Promise 链固定一批任务跑完,脚本或批处理任务中途新增不方便,链会越来越长

选型其实就看两点:任务是无穷无尽地来,还是固定一批跑完;失败的时候你希望阻塞整个队列,还是跳过这一条继续往下走。这两点想清楚,写法基本就定了。

写完必须检查的四个坑

  1. 异常路径有没有放锁请求抛错时如果忘了释放标志或信号量,队列会永久卡死。故意发一条会失败的请求,看后面的任务还能不能继续跑,这是最快的复现方式。
  2. 超时有没有设串行之后单条卡住会拖住后面所有任务。每个调用都要有自己的超时和重试上限,不能指望外层兜底。
  3. 有没有把串行当成限流并发 1 不等于每秒只能发一个。如果对方是按时间窗口限流,串行还得叠加上间隔或退避策略,两者是两件事。
  4. 进程数有没有被忽略代码里锁住了,但服务起了多个实例,实际并发还是等于实例数。这类问题在多副本部署里最常见,锁必须放在真正共享的那一层。

这四个坑里,异常路径和超时是自测能发现的,进程数那个得结合部署方式看。如果你不确定自己下游的具体限制形态,可以到 /guides 里翻并发和限流相关的分档讨论,再回来定参数。

怎么验证真的只有一个在跑

写完不要只靠读代码下结论,跑一轮才能确认。最直接的办法是进入和退出时各打一条带任务标识的日志,看时间区间有没有交叠;代码看起来是串行的、日志里区间重叠的情况并不少见。

  • 进入前后各记一条带任务标识的日志,比对时间区间是否重叠
  • 在调用前后做加减计数器,断言峰值不超过 1,跑一批任务就能暴露问题
  • 观察报错形态变化:从限流报错变成排队变慢,说明限流真生效了,剩下的是延迟预算问题
  • 上线前用接近真实体量的任务跑一遍,别拿三条测试数据判断队列长度够不够

还有一个容易忽略的角度:串行之后整体耗时会被拉长,你要确认这个时长在业务可接受范围内。单次延迟的基准可以去 /tools/speed-test 现场测,具体数值以页面当时显示为准。

下一步把上面四种验证方式在你的服务上跑一轮,如果发现瓶颈其实在下游配额而不在自己代码,就到 /rankings 和 /sites 看看同类中转和线路的实际表现,再决定是要继续锁到 1 还是换条路走。

常见问题

并发数设成 1 是不是就等于把接口变慢了?

总耗时通常是变长的,因为任务从并行变成排队。但换来的是不再撞限流、不再出现资源争抢导致的随机失败,整体成功率往往更高。判断标准是:你的瓶颈在下游配额还是在自己算力。如果下游明明能并发,只是你懒得调,那串行就是自己给自己加戏。

队列越堆越长怎么办?

先分清是消费慢还是生产快。如果是消费慢,检查单次调用延迟有没有异常,用站内测速页面量一下基线;如果是生产快得离谱,就该在上游做节流或者干脆拒绝,而不是让队列无限膨胀吃内存。给队列设一个上限并明确满了之后的行为,是比调大队列更实在的做法。

多进程或者多副本部署时并发 1 还成立吗?

不成立。函数里的锁只在单个进程内有效,起了 N 个实例实际并发就是 N。这时候要么把并发控制挪到外部——比如用一个共享的队列服务或数据库行锁;要么把实例数压到 1 并接受单点风险。先确认你的部署形态,再决定锁写在哪一层。

本文依据各平台公开文档与官方公告整理,只做中文归纳;具体做法、参数与限制以官方文档为准。