@
JasperYanky 总结一下:本文内的问题是,在基于 gevent 的 http server 上大量使用 requests 时速度很慢,甚至会超时,看起来像阻塞了一样。最后楼主通过调大 pool manager 的 maxsize 解决了问题。
然后我通过类似 #69 的测试方法复现了这个问题,并横向测试了其他方案的一些表现。测试用例方面为了排除外部变量,与 #69 的第二步不同,我没有选择 baidu 的页面,而是用第一步中自己的 /hello 页面来进行测试。即完整的测试方案为:
0. 写一个简单的 http server,提供两个接口。第一个 /hello 简单返回 "hello";第二个 /world 会通过 http 访问 /hello 然后把拿到的东西返回出去(不使用公共 session,裸 requests.get )
1. ab -c 100 -n 5000 http://127.0.0.1:5000/hello
2. ab -c 100 -n 5000 http://127.0.0.1:5000/world
先来看一下这个用例,会发现第二步比第一步多的就只是一次 /hello 的访问,因此理论上第二步的 QPS 应该为第一步的一半(在未达到处理极限的前提下)。然后测试数据如下(全部测试跑在我的小本本上,默认 2 个 worker,CPython3.6,gunicorn,gevent,测试有偏差,15% 以内大概,看个比例就行)
go 8784 3544
--------------------------
gevent+requests 1079 261
gevent+httplib2 988 336
gevent+gcurl 1079 562
--------------------------
sanic+aiohttp_client 6631 1513
以 go 版本为对照组,第二步 QPS 能达到第一步的 40%,基本满足预期(而且绝对值上也是最高的)。然后第二组就是有问题的 gevent + requests 了,第二步只有第一步的 24%。看来确实有问题,这里考虑一下,io 已经被 patch 成异步的了不会阻塞,那多半是 requests 自己慢,再想一下它那些高级的接口和冗长的面向对象代码,可能是慢的原因,于是把第二步中的 http client 换成了更底层的 httplib2,发现 QPS 提高到了 336 ( 34%),效果显著但还不够好。于是想进一步替换成更高效的 pycurl,同时为了对接 gevent,找了个 gcurl 包,这次 QPS 达到了 562 ( 52%),效果拔群。到此基本可以确定,是 requests 代码本身的执行效率低导致的问题,与 gevent 应该没什么关系。当 requests 不能满足你的需求时,可以换一个更快的 http client。
最后说一下绝对 QPS 的问题,gevent 下的 /hello 接口都只有大约 1000,比 go 低了一个数量级,造成这个问题的原因与 requests 类似,都不是 io 的问题,而是 python 代码本身执行效率低。即使不用 monkey patch,改成原生的 tornado,/hello 的 QPS 也只有 1200 左右。上面测试数据的最后一组我用了 sanic 框架,这个框架基于原生 asyncio 并把 ioloop 和 httpparser 都替换成了 C 版,才使 /hello 接口的 QPS 接近 go,但因为没有用 C 版的 http client,/world 的 QPS 比仍然偏低( 22%)。综上,当你的 python 代码执行效率遇到瓶颈的时候,要么简化代码,要么上 C 模块,要么也可以考虑换成 go。