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

2017-08-15 17:48:12 +08:00
 woostundy
该怎么优化呢?异步 http 客户端的库哪个比较靠谱?
14780 次点击
所在节点    Python
101 条回复
terrawu
2017-08-16 18:01:56 +08:00
@woostundy 你的这种情况如果没有突发的高并发数目的话,还不如增大 requests.Session 的 pool size 试下

https://stackoverflow.com/questions/18466079/can-i-change-the-connection-pool-size-for-pythons-requests-module
ChangHaoWei
2017-08-16 18:04:20 +08:00
@woostundy socket.io 是即时通讯框架。支持 websocket。可以主动联系 c 端。也就是请求和返回可以分开。
icedx
2017-08-16 18:05:41 +08:00
Flask+Uwsgi 吧
Flask+Gevent 数据库部分有小 Bug
前几天经常拉倒整个进程 换了之后没出过毛病
terrawu
2017-08-16 18:09:13 +08:00
@ChangHaoWei 没有,我手写的,我情况和楼主不一样,我的类似 gateway, 需要同时请求 N 个后端 http 服务,突发比较高。用的全局 dict 存多个 httplib2.HTTP 实例,跑的很流畅。
terrawu
2017-08-16 18:10:23 +08:00
不过已经被我用 go 改写了,效果好太多。
neoblackcap
2017-08-16 19:30:56 +08:00
@woostundy 你的 QPS 没有增长,那么你是 gunicorn 的 worker 跑满了吗,要不然不应该线性增长吗?要不直接加大 gunicorn 的 worker 看看,反正你数据库又不是瓶颈
yonoho
2017-08-16 19:32:20 +08:00
@1iuh 请问这个问题应该怎么复现?我用最简化的测试方法(/a 访问 1000 次 /b, 每个 /b 访问 1 次 /c, /c ),1000 次 10s+,没感觉有阻塞
yonoho
2017-08-16 19:33:35 +08:00
patch #67: /c sleep 1,访问一次 /a 10s+
1iuh
2017-08-16 21:13:10 +08:00
@yonoho #68 我是这样测试的。
用 Gunicorn + Gevent + Flask 启动 8 个 worker。
测试 1: flask 什么都不做,直接 return "hello world"。 用 ab 测试 100 个并发 100000 次请求。结果 QPS 700 左右。
测试 2: flask 直接 requests.get 百度,然后 return result.text。测试方式一样,结果 QPS 只有 40 左右。
chenqh
2017-08-16 21:33:56 +08:00
@terrawu golang 性能好了多少?
terrawu
2017-08-16 22:23:47 +08:00
@chenqh 好了非常多,但也不能说是 python 不行。

因为我的程序需要聚合一下后端的数据,然后做一个 digest(一些哈希操作), 这个步骤会比较吃 CPU。
后来 IO 方面的瓶颈都优化完毕后,程序的瓶颈就体现在这里了

1. python 代码效率低,太耗 CPU
2. GIL 不能用多核
3. 内存耗费也比较大。

所以优化到这一步时候,用 go 改写就很适合了,所谓性能的话,提升了几十倍吧。
chenqh
2017-08-17 08:34:42 +08:00
@terrawu 好吧,反正我的 python web 程序,CPU 基于不到 10%,
lolizeppelin
2017-08-17 09:01:17 +08:00
你现在是 a 被访问一次就去 a 就去访问 b 一次?

和数据库用链接池一样 requests 的 session 也池化
一开始就建立一定数量的链接然后维护心跳
不然老是 socket connect 也很耗资源的

简单实现可以炒 py redis 的写法试试
shiina
2017-08-17 09:35:29 +08:00
从楼上兄弟链接去旧贴看到了 Livid 老哥
[doge]
lolizeppelin
2017-08-17 10:23:42 +08:00
详细看了下源码

pool_key = (scheme, host, port)

也就是说默认 requests 的默认的池在你目的地只有一个的时候是无效的

所以一个是直接用 session 做池,还有一个办法是重写 HTTPAdapter

好像 init_poolmanager 的 pool_kwargs 传入 maxsize 就能直接池化单个目的地了!!

我操都封装好了嘛...........
woostundy
2017-08-17 10:28:35 +08:00
@lolizeppelin 我试过了,并没有效果。这需要多个请求用同一个 session,而 flask 不同请求之间没法用同一个 session。

我又尝试在外层弄一个全局的 session,HTTPAdapter 里把 maxsize 调到 100,但是效率更低了,超过一半的请求都超时。
lolizeppelin
2017-08-17 10:34:22 +08:00
理论上不会那么差啊 池化以后是长链接了 单纯的 http 请求后端没问题的话前端 qps 50 应该不成问题的

等等, 你是单进程的还是多进程的?
woostundy
2017-08-17 10:50:12 +08:00
@lolizeppelin 之前是 8 个子进程( 4 核 CPU ),考虑到 CPU 没压力,瓶颈在 IO 这,又开到了 16 子进程,然后能跑到 50 qps 了,再高就会出现 HttpsConnectionError 了。
另外,你确定不是同一个请求里的 session 能共用同一个长连接?
terrawu
2017-08-17 11:09:59 +08:00
> 弄一个全局的 session,HTTPAdapter 里把 maxsize 调到 100

这个太低,调到 1000 试下,还不行的话,你的需求就用 httplib2 实现吧。
lolizeppelin
2017-08-17 11:16:07 +08:00
HTTPConnectionPool 维护的是 HTTPConnection,再下面就是 socket 了

所以 init_poolmanager 的时候增加 maxsize 参数就增加了 HTTPConnection 的数量,
conn.urlopen 的时候就是 HTTPConnectionPool.urlopen
HTTPConnectionPool 会从自己的队列里取出一个 HTTPConnection 去访问 url

所以只要你 request 的 session 是同一个(用 session.request )
那么你的请求都是从同一个 HTTPConnectionPool 里出来的,所有 con 都没调用过 close (除非你主动 session.close )
都是长链接的,可以被复用

要不这样你抛开你的框架

直接写个单文 fork 8 进程用 requests 去请求你 b 服务器的一个接口 看看 qps 这样不就知道是不是 requests 的问题了

测玩可以改成协程的试试性能有没有提高

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

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

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

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

© 2021 V2EX