用Python asyncio写生产级服务有一阵子了,踩了不少坑。分享一下最容易出问题的三个点:超时、取消、背压。

## 1. 超时:永远不要裸await

最常见的错误:

# 错误:永远卡住
result = await fetch_data()

正确做法:

# 正确:带超时
try:
result = await asyncio.wait_for(fetch_data(), timeout=5.0)
except asyncio.TimeoutError:
# 处理超时
...

## 2. 取消:CancelledError要正确处理

当任务被取消时(比如客户端断开连接),asyncio会抛CancelledError。很多人不知道要正确处理:

async def long_task():
try:
while True:
await do_work()
except asyncio.CancelledError:
# 清理资源
await cleanup()
raise # 重要:重新抛出!

注意:一定要raise回去,否则任务不会真正取消。

## 3. 背压:不要无限并发

asyncio最爽的地方是并发,但最容易踩的坑也是并发太多把下游打挂。

错误做法:

# 错误:无限并发
tasks = [fetch(url) for url in urls]
results = await asyncio.gather(*tasks)

如果urls有10000个,同时发10000个请求,下游直接挂掉。

正确做法:用Semaphore控制并发数:

sem = asyncio.Semaphore(100) # 最多100并发

async def bounded_fetch(url):
async with sem:
return await fetch(url)

tasks = [bounded_fetch(url) for url in urls]
results = await asyncio.gather(*tasks)

## 实战经验总结

1. 所有外部调用都要加超时,包括数据库、HTTP、RPC
2. 长任务要正确处理CancelledError,做资源清理
3. 并发一定要限流,Semaphore是你的好朋友
4. 用asyncio.gather的return_exceptions=True,避免一个任务失败拖垮全部

大家用asyncio写生产服务的时候,还踩过什么坑?