这类问题我又碰上了一次。用 FastAPI 加 AsyncOpenAI 写批量摘要,在请求处理函数内部创建 asyncio.Semaphore(5),压测 100 并发仍然把上游打到 429,日志里同时进行的请求数是满的。

第一反应是「信号量只包住了创建任务,没包住 await」,验证下来不成立。

把日志和配置对完,能解释这个现象的基本就这几处。信号量在每次请求里新建,等于没有共享;acquire 之后没有 release,异常路径直接漏掉;gather 把所有协程一次性启动,限流点在内部来不及生效。

上来先打印信号量的对象地址确认是否共享

python -c "import asyncio;s=asyncio.Semaphore(5);print(id(s))"

随后把信号量提到模块级只建一次

python -c "import asyncio;SEM=asyncio.Semaphore(5);print('global')"

再往下用 async with 保证异常也能释放

python -c "print('async with SEM: await call()')"

等前面都确认过,再确认在飞请求数不超过设定值

python -c "print({'inflight':5,'peak':5})"

怎么算真的好了,看的是同一批现象。压测期间在飞请求始终不超过 5,429 归零,总耗时只比不限流多两成。

信号量必须是被所有请求共享的同一个对象,放进函数体里就是每请求一把锁,看起来写了限流其实没有。改动前把原配置备份一份,出问题能马上回滚,这比任何技巧都实用。

作者 9823189