问题引入
之前实习的时候,组里有用 Python 写的项目,部署方式是 nginx + gunicorn,据组里正式工说,这样可以提高并发量,于是我就那么用了。不过心里一直想自己测一下,看看到底 gunicorn 好在哪了。于是抽空用 JMeter 分别对 gunicorn 和 Flask 自带的Server进行了测试和对比。
Gunicorn 和 WSGI 简介
先说说 WSGI。啥是 WSGI?Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。
咱们平时写的 Flask 的业务代码,是 Web application,是无法处理客户端发的那些 HTTP 请求的,需要一个实现了 WSGI 的 Server 帮忙处理 HTTP 请求,让 HTTP 请求变成业务代码可以处理的形式之后再给 application 的业务代码,然后业务代码进行一系列处理,把处理结果给 Server,Server 再把这个结果封装后给客户端。用这样的方式,就可以专心的写业务代码,不用考虑怎么解析 HTTP 请求,怎么封装 HTTP 回复,这些都是 Server 的工作。
Gunicorn 就是这样一个实现了 WSGI 的 HTTP Server,它在 Flask 和 客户端之间充当一个翻译的角色,并且相比于 Flask 自带的 Server,有很好的并发性能。
那有人说,我平时用 Flask 也不知道什么 WSGI 啊,怎么也可以跑代码?那是因为 Flask 内置了一个小的 Server。
这个输出信息大家肯定不陌生,里面就说了,这是个开发用的 Server,不要把它用于生产部署。
实验环境
阿里云最便宜的学生优惠服务器(因为 gunicorn 只能在 Linux 系统上用)
XShell 用来连接云服务器,JMeter 用来压测,Flask 框架用来编写业务代码,Python3,没了。
相关命令和配置
启动 gunicorn
Gunicorn 可以用命令行或者配置文件方式启动,我这里用的是命令行启动:
gunicorn -D -w 4 -b 0.0.0.0:80 testFlask:app
gunicorn -w 5 -b 0.0.0.0:80 --worker-class gevent --threads 10 testFlask:app
,
gunicorn -w 3 -b 0.0.0.0:80 --worker-class gevent testFlask:app
这里面 w 就是进程数量,worker-class 是选用的 gunicorn 的运行模式(Gunicorn 有四种模式,-D 就是后台运行,你看不到输出的那种),--threads 就是每个进程的线程数量,testFlask:app 是设置app的名字,我这里的 Flask 文件命名为 testFlask.py。类似这样的,我中间换了很多次参数。
启动 Flask 自带 Server
python3 testFlask.py
即可,名字改成你自己的 Flask 文件名。
设置 JMeter
对 JMeter 设置参数,我主要通过设置线程数和循环次数来测试,时间均为一分钟。JMeter 怎么安装就不赘述了。
设置好测试计划之后,运行E:\jmeter\apache-jmeter-5.4.1\bin>jmeter -n -t "Test Plan.jmx" -l testPlan/result/result.txt -e -o testPlan/webreport
,可以用此命令在命令行中运行 JMeter 测试计划:-t
后面跟你生成的 jmx 文件;-l
后面跟你存放测试报告的 txt 文件的路径,-o
后面跟你存放 Web 版报告的路径。
编写 Flask 的业务代码
Flask 代码写的很简单:
@app.route('/test', methods=['POST','GET'])
def test():
return {'msg':'hello world'}
if __name__ == '__main__':
app.run(port='80', host='0.0.0.0')
复制代码
其它命令
一些其它命令:ps aux | grep python
,用来找到运行的 Python 进程,kill -9
,找到之后,将它 kill 掉,从而更改参数。
进行实验
一分钟500个请求
一开始我对并发量没什么概念,非常温柔的设置了这个数量的请求,结果 gunicorn 和 Flask 自带的 Server 没有啥区别,gunicorn 还挂了两个。
用 JMeter 生成了报告,上面的图是 Flask 自带的 Server,下面的是 gunicorn(以下都是这种顺序)。
一分钟1500个请求
差不多。
一分钟15000个请求
我决定加大药量,把线程数设置为了5000,循环数设置为了3,一分钟发15000个请求。先看 Flask 自带的 Server:
可以看出,表现得很差,67.06%的请求都失败了。接着我用 gunicorn 运行,开了5个 worker,每个 worker 20个线程。
可以看到,确实效果好了不少,不管是成功率还是Throughput(吞吐量,这里用每秒钟处理的事务数反映)。可是它还是没有我预期的那么好,我还以为可以全都成功呢 /(ㄒoㄒ)/~~
接下类,我对默认模式下的 worker 和 threads 数量进行了各种尝试,输出的结果始终都差不多;又换了据说并发量更大的 gevent 模式,尝试了 worker 和 worker-connections(每个worker 的最大连接数),但是结果都差不多 /(ㄒoㄒ)/~~
感觉可能是阿里云这学生优惠的最低配置服务器比较拉跨吧(机器越好,这种多进程多线程的程序才越能发挥威力),又或者是我 JMeter 测试的方式不太好,总之,gunicorn 在我这辣鸡服务器上比起 Flask 自带的 Server 确实显著的胜出了,虽然效果没有我预期中的那么那么好。
心得体会
- 这个实验让我感觉到了像微信这种国民应用有多难做,我这一分钟15000个请求,而且没有任何的业务处理,只是返回个键值对,都吭哧吭哧的,人家那服务一秒钟估计调用就几亿次了,结果用着竟然那么流畅,真的很厉害。
- 后端开发还是很有挑战的,我这个实验不涉及数据库,那些复杂应用,不仅 HTTP Server 要处理的过来,数据库也得抗住啊,或者你用了 Redis 这种缓存,也得保证它的高可用性。总之,后端开发,真的博大精深。