Python 2.7 Gunicorn + Flask,有大量的第三方服务 http 请求, requests 库阻塞导致出现性能瓶颈

2017-08-15 17:48:12 +08:00
 woostundy
该怎么优化呢?异步 http 客户端的库哪个比较靠谱?
14726 次点击
所在节点    Python
101 条回复
gouchaoer
2017-08-16 11:37:29 +08:00
@aisk gevent 就一个把同步 api 来 hack 成异步的嘛,但是 controller 里面凡是涉及 IO 的东西都能弄成异步的?(包括数据库、redis 等等),这不可能的吧。。。。你能给个 github 的 repo 例子让我瞻仰一下么?
aisk
2017-08-16 11:47:28 +08:00
@gouchaoer 网络 IO 是可以的,文件 IO 不行,因为 gevent 直接 pactch 的自带的 socket 等模块。不过有些直接用 C 来实现的模块,内部没有使用 Python 的 socket 模块,就不会被 patch 到了,比如最常见的那个 mysql 库。
aisk
2017-08-16 11:48:54 +08:00
gouchaoer
2017-08-16 11:50:33 +08:00
@aisk show me the code
neoblackcap
2017-08-16 13:16:18 +08:00
楼主你需要同步返回给客户端吗?假如是的话,flask 是解决不了这样的,哪怕是 Gunicorn + gevent 也是一样。毕竟你用了大量的同步库,比如数据库连接什么的,那 gevent 肯定也是堵塞的。你可以做的只有用另外的语言或者框架来替代这个 API,比如上面封装一层 Tornado 或者 Golang 写的模块。
如果是异步的接口,那么你可以将所有请求扔到 celery,由 celery 处理,celery 来起一个 gevent 类型的 worker,gevent 的 worker 处理所有的网络请求,完美兼容你的 requests 逻辑。前提是你的接口是异步的,celery 的 worker 能独立地返回请求给客户端。
aisk
2017-08-16 13:16:20 +08:00
@gouchaoer suck my dick
neoblackcap
2017-08-16 13:22:13 +08:00
@woostundy 还有就是你发现 gevent 的 monkeypatch 用了不起效,很有可能是你已经载入了 Python 的底层网络库,那么 gevent 的 monkeypatch 就不起效了,monkeypatch 必须在网络库载入之前使用,否则无效。
requests 对 gevent 的兼容性挺好的,毕竟 requests 是一个纯 Python 的 http 库,gevent 能完美支持的,一般不支持的是因为底层用了 C 库,monkeypatch 没法对这些库进行打补丁导致,具体例子就是 mysql-python。
woostundy
2017-08-16 13:43:52 +08:00
多谢各位。先解释下上面的测试,我单独用 gevent 的 monkeypatch,requests 的确没效果,gevent 显示 httplib=True 已不再支持。但用 gunicorn 开 gevent 是有效的。
Dominator
2017-08-16 13:48:40 +08:00
恕我直言。。你可以用 grequests 先试试,如果能解决问题就解决了。那种一上来就换大框架换到 tornado 的,不敢恭维。
upwell
2017-08-16 13:53:26 +08:00
有个做法是,把外部依赖的接口改写成用异步框架实现的,不走 flask,其它的请求仍然走 flask。
terrawu
2017-08-16 14:02:49 +08:00
一群小学生,没有踩坑就不要瞎猜,受不了了。看我 #6 回复。
aisk
2017-08-16 14:28:42 +08:00
受不了了,直接贴代码:

https://gist.github.com/aisk/b1d8c07b96a8fecb319c7902773f9c0a

安装 gevent, flask, requests,然后用 gunicorn fuck:app 来启动项目。

访问 http://127.0.0.1:8000/suck 这个地址,会用 requests 去访问一个外部的会卡十秒的接口,这个时候再访问 http://127.0.0.1:8000/fuck,这个地址会因为当前进程被 requests 卡住,所以不能响应,要等 /suck 响应了之后才能响应。

然后启动命令改成 gunicorn a:app -k gevent,这个时候 gunicorn 会自动帮你打 monkey patch,因此你的代码一行也不用改。

然后访问 /suck,再访问 /fuck,你看现在能不能正确响应?

说 gevent 和 reuqests 不能配合的,都是老黄历了,不要出来误人子弟了,说打 monkey patch 的时候要加 httplib=True 也是老黄历了。直接推荐别人换 tornado 或者 asncio,甚至推荐上 celery 的,本来加一个参数就解决的问题,是要让别人重写项目?
terrawu
2017-08-16 14:43:11 +08:00
楼上的大兄弟,你的代码的确不会 block,但是你测过 “并发度” 么?

requests 的 streaming 和 pool size 的机制会导致并发瓶颈,monkey patch 的并不能帮助这个问题。
而 httplib2 没有 streaming 的接口,反而能提高真的的并发。
clino
2017-08-16 14:46:01 +08:00
我建议你的应用和 openresty 配合起来用,要用 request 的地方都改用 openresty 来实现
aisk
2017-08-16 14:50:05 +08:00
@terrawu 你是想解决楼主的问题,还是想从我的回复里挑个刺儿,怼我一下?
woostundy
2017-08-16 14:51:24 +08:00
@terrawu 兄弟,你所说是最贴合我的实际情况的。
terrawu
2017-08-16 14:51:53 +08:00
@aisk 我是在售卖我的 #6 回复的优点。
yangxin0
2017-08-16 14:54:30 +08:00
首先你们业务是猛增吗? 如果是我觉得可以快速加服务器解决当前的困境。 然后可以查一查是请求处理哪个环节出了问题。问题没定位出来不要贸然的换语言、换框架,打猴子补丁。
woostundy
2017-08-16 14:55:36 +08:00
@aisk 我之前说的测试方法的确有问题,用 Gunicorn 开了 gevent 实际是有效的,但是当并发量稍微一大还是会出现阻塞的情况。另外我自己单独写个 py 脚本打上 monkey patch 没效的,而且在官方文档里提到用 grequests,综上让我误以为是 gevent 没生效。

所以你说的也是对的,Gunicorn+gevent 可以让 requests 变异步,但并发高了出问题应该是 terrawu 所说的原因。
gouchaoer
2017-08-16 14:57:09 +08:00
@aisk gevent 在 hack 掉 requests 底层 i 之后遇到 IO 会使用协程主动让渡 cpu 到别的协程没错,不只 httpclient 有 IO,mysql 以及 redis 都有 IO 的,也就是说你 mysql/redis 的 client 不使用协程的话如果 qps 大了,那么 mysql/redis 阻塞了之后那么别的协程就无法调度。。。。我能问问你使用 gevnet 之后 mysql 之类的涉及 IO 的库都自动切换到协程版本? python 这么神通广大?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/383174

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX