ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

aiohttp 和 requests 混用的 3 个坑,第 2 个我踩过

aiohttp 和 requests 混用的 3 个坑,第 2 个我踩过 aiohttp 和 requests 混用的 3 个坑第 2 个我踩过Python 网络代码写得多了免不了要混用同步和异步库。requests 是同步的aiohttp 是异步的——这俩一起用坑不少。今天说 3 个最容易踩的给后来人提个醒。坑 1在 async 函数里直接调 requests最常见错误importasyncioimportrequestsasyncdeffetch_data():resprequests.get(https://api.example.com/data)# 阻塞整个事件循环returnresp.json()requests 是纯同步库调用时会阻塞当前线程。放在 async 函数里会卡住整个事件循环直到请求返回。其他所有异步任务都被冻住。这是个非常隐蔽的 bug——代码能跑逻辑看起来没问题但在高并发场景下性能会突然垮掉。一个请求慢所有请求都跟着等。正确做法用 aiohttp 替代。importaiohttpasyncdeffetch_data():asyncwithaiohttp.ClientSession()assession:asyncwithsession.get(https://api.example.com/data)asresp:returnawaitresp.json()如果实在不能换可以用loop.run_in_executor包装importasyncioimportrequestsasyncdeffetch_data():loopasyncio.get_event_loop()respawaitloop.run_in_executor(None,requests.get,https://api.example.com/data)returnresp.json()但这只治标——请求依然阻塞一个线程不推荐长期用。过渡期可以长期还是要统一到异步库。坑 2Session 关闭时机不对我踩过这个这是我自己踩过的坑印象特别深。如果你同时用 requests 和 aiohttp要注意它们的 Session 生命周期管理方式不同# requests 的 Session 用 with 语法withrequests.Session()assession:respsession.get(url)# 用完自动关闭# aiohttp 的 Session 也要用 async withasyncwithaiohttp.ClientSession()assession:respawaitsession.get(url)# 用完自动关闭我之前遇到的情况是这样的有个定时任务同时调用两个接口一个用 requests一个用 aiohttp。requests 的 Session 正常关闭了但 aiohttp 的 ClientSession 忘了写async with直接session aiohttp.ClientSession()然后用完没调await session.close()。结果连接池没释放内存里堆了几百个 TIME_WAIT 连接进程跑了两天直接 OOM。最后看日志才发现是连接泄漏。教训不管同步还是异步Session 一定要显式关闭。尤其是 async 代码里await session.close()不能省。进阶一点可以用上下文管理器统一处理importaiohttpimportrequestsclassHTTPClient:def__init__(self):self._req_sessionNoneself._aio_sessionNonedefrequests_session(self):ifself._req_sessionisNone:self._req_sessionrequests.Session()returnself._req_sessionasyncdefget_async(self,url:str):ifself._aio_sessionisNone:self._aio_sessionaiohttp.ClientSession()returnawaitself._aio_session.get(url)asyncdefclose(self):ifself._aio_session:awaitself._aio_session.close()self._aio_sessionNonedefclose_sync(self):ifself._req_session:self._req_session.close()self._req_sessionNone这样调用方只管调用await client.close()不会漏掉。坑 3超时配置不一致requests 和 aiohttp 的超时配置接口完全不一样混用时特别容易搞混。requests 的超时importrequests# 传入一个数字 总超时传入元组 (连接超时, 读取超时)resprequests.get(url,timeout(3.05,10))# 3.05 秒连接超时10 秒读取超时aiohttp 的超时importaiohttp timeoutaiohttp.ClientTimeout(total30,# 总超时 30 秒connect3,# 连接超时 3 秒sock_read10,# 读取超时 10 秒)asyncwithaiohttp.ClientSession(timeouttimeout)assession:respawaitsession.get(url)这里有个细节requests 的(3.05, 10)第一个数字是连接超时aiohttp 的connect3也是连接超时语义一样但默认值完全不一样。requests 默认超时是None永久等待aiohttp 的ClientTimeout()默认 total 是 300 秒。如果两边都依赖默认值超时行为会完全不同。混用时我建议统一用一个配置字典importaiohttpimportrequests TIMEOUT_CONFIG{connect:3,read:10,}# requests 用元组resprequests.get(url,timeout(TIMEOUT_CONFIG[connect],TIMEOUT_CONFIG[read]))# aiohttp 用 ClientTimeouttimeoutaiohttp.ClientTimeout(totalTIMEOUT_CONFIG[connect]TIMEOUT_CONFIG[read],connectTIMEOUT_CONFIG[connect],sock_readTIMEOUT_CONFIG[read],)asyncwithaiohttp.ClientSession(timeouttimeout)assession:respawaitsession.get(url)这样超时配置只改一个地方不会出现一边改了另一边忘了的问题。写在最后混用 requests 和 aiohttp 不算优雅但实际项目里很常见——比如老代码用 requests新功能想上 asyncio没法一次全替换。关键三条async 函数里不要直接调 requests用run_in_executor或直接换 aiohttpSession 一定显式关闭async 代码里await session.close()不能省超时配置统一管理避免两边不一致真要全异步从一开始就全用 aiohttp / httpx省心。httpx 同时支持 sync 和 async 两套接口迁移成本最低是一个不错的折中方案。另外补充一点如果你的项目里同时存在 sync 和 async 代码尽量把入口分开——同步逻辑走同步函数异步逻辑走异步函数中间不要互相调用。asyncio.run() 在 sync 函数里调用 async 函数是可以的但反过来async 函数里调用 blocking sync 函数就一定会卡事件循环。分清楚这个边界能少踩很多坑。
返回列表