Gunicorn vs Flask 自带 Server :性能测试

3,916 阅读5分钟

本文正在参与 “性能优化实战记录”话题征文活动

问题引入

  之前实习的时候,组里有用 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。

image.png

  这个输出信息大家肯定不陌生,里面就说了,这是个开发用的 Server,不要把它用于生产部署。

实验环境

  阿里云最便宜的学生优惠服务器(因为 gunicorn 只能在 Linux 系统上用)

image.png

  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 怎么安装就不赘述了。

image.png

  设置好测试计划之后,运行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 还挂了两个。

image.png

image.png

  用 JMeter 生成了报告,上面的图是 Flask 自带的 Server,下面的是 gunicorn(以下都是这种顺序)。

一分钟1500个请求

image.png

image.png

  差不多。

一分钟15000个请求

  我决定加大药量,把线程数设置为了5000,循环数设置为了3,一分钟发15000个请求。先看 Flask 自带的 Server:

image.png

  可以看出,表现得很差,67.06%的请求都失败了。接着我用 gunicorn 运行,开了5个 worker,每个 worker 20个线程。

image.png

  可以看到,确实效果好了不少,不管是成功率还是Throughput(吞吐量,这里用每秒钟处理的事务数反映)。可是它还是没有我预期的那么好,我还以为可以全都成功呢 /(ㄒoㄒ)/~~

  接下类,我对默认模式下的 worker 和 threads 数量进行了各种尝试,输出的结果始终都差不多;又换了据说并发量更大的 gevent 模式,尝试了 worker 和 worker-connections(每个worker 的最大连接数),但是结果都差不多 /(ㄒoㄒ)/~~

  感觉可能是阿里云这学生优惠的最低配置服务器比较拉跨吧(机器越好,这种多进程多线程的程序才越能发挥威力),又或者是我 JMeter 测试的方式不太好,总之,gunicorn 在我这辣鸡服务器上比起 Flask 自带的 Server 确实显著的胜出了,虽然效果没有我预期中的那么那么好。

心得体会

  • 这个实验让我感觉到了像微信这种国民应用有多难做,我这一分钟15000个请求,而且没有任何的业务处理,只是返回个键值对,都吭哧吭哧的,人家那服务一秒钟估计调用就几亿次了,结果用着竟然那么流畅,真的很厉害。
  • 后端开发还是很有挑战的,我这个实验不涉及数据库,那些复杂应用,不仅 HTTP Server 要处理的过来,数据库也得抗住啊,或者你用了 Redis 这种缓存,也得保证它的高可用性。总之,后端开发,真的博大精深。