这类问题我又碰上了一次。用 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 归零,总耗时只比不限流多两成。
信号量必须是被所有请求共享的同一个对象,放进函数体里就是每请求一把锁,看起来写了限流其实没有。改动前把原配置备份一份,出问题能马上回滚,这比任何技巧都实用。
