给下载任务加了 Semaphore 限流,目标同时跑五个,实际并发冲到八十多个,机器带宽被打满,接口开始大量超时。乍看像缓存或者权限的锅,追下去往往不是。
sem = asyncio.Semaphore(5)
async def one(sem, url):
async with sem:
return await fetch(url)
await asyncio.gather(*[one(sem, u) for u in urls])
追下去是这么回事:sem 是在 for 循环里面创建的,每个任务都拿到一个独立的信号量,各自计数各自放行,等于没有共享同一个上限,限流自然不发生作用。
「信号量限流失效」相关的验证我通常做两遍,改完一次、隔天再看一次,确认不是临时生效。一次只改一处,改完立刻验证,几个改动叠在一起,出问题就说不清是谁导致的。
先做最小复现,能稳定跑出来的问题,排查成本会低一个数量级。
具体做法是,信号量在循环外创建一次,作为参数传进每个任务;确认任务拿到的是同一个对象,可以打印 id 核对一下;并发上限按下游的承受能力来定,不要凭感觉拍脑袋。
验收就看一点,同时在途请求数始终不超过五个,抓包或者计数日志都能对上。
