目录
4、AsyncIO Workers(gthread, gaiohttp)
Gunicorn(绿色独角兽Green Unicorn的简称),是一个Python WSGI HTTP server,只支持在Unix系统上运行,来源于Ruby的unicorn项目。
Gunicorn服务器广泛兼容各种web框架,实现简单,服务器资源少,而且速度相当快。官方建议Nginx+gunicorn性能最好。
Gunicorn的优势在于,它采用的是pre-fork worker模式, gunicorn在启动时,会在主进程中预先fork出指定数量的worker进程来处理请求,即一个master进程管理多个worker进程(管理进程:master,工作进程:worker),所有请求和响应均由Worker处理。
Master进程是一个简单的loop, 监听 worker不同进程信号并且作出响应。这样可以减少频繁创建和销毁进程的开销,但是一个进程相对占用资源,会消耗大量内存。
Gunicorn推荐的worker数量是:(2*$num_cores)+1。
https://github.com/benoitc/gunicorn/blob/master/gunicorn/app/wsgiapp.py
$ pip install gunicorn
如果应用程序可能需要在请求处理期间暂停很长一段时间,这时建议安装Eventlet或Gevent,异步执行worker。
$ pip install greenlet
$ pip install eventlet
$ pip install gevent
基本使用格式:
$ gunicorn [OPTIONS] [WSGI_APP]
使用示例,在/home/myproject目录下,创建test.py文件,如下:
def app(environ, start_response):
"""Simplest possible application object"""
data = b'Hello, World!\n'
status = '200 OK'
response_headers = [
('Content-type', 'text/plain'),
('Content-Length', str(len(data)))
]
start_response(status, response_headers)
return iter([data])
运行:
$ gunicorn --workers=8 --chdir /home/myproject test:app
常用参数:
-c CONFIG, --config=CONFIG 指定配置文件
-b BIND, --bind=BIND 绑定运行的主机和端口
-w INT, --workers INT 用于处理worker进程的数量,默认为1
-k STRTING, --worker-class STRTING 指定要使用的工作模式,默认为sync异步,类型:sync, eventlet, gevent, tornado, gthread, gaiohttp
--threads INT 处理请求的工作线程数,使用指定数量的线程运行每个worker。为正整数,默认为1
--worker-connections INT 最大客户端并发数量,默认1000
--chdir 在加载应用程序之前切换目录
更多参数详情参考:$ gunicorn -h
Gunicorn的工作模式是通过work_class参数配置的值,默认缺省值为sync。
当前支持的工作模式类型清单:sync, eventlet, gevent, tornado, gthread, gaiohttp
默认worker class,为sync worker,是最简单的工作模式,在cpu和带宽方面会消耗资源。性能低。
大多数情况下,采用的worker类型是同步方式,也就是说一次仅处理一个请求。这种模型方式是最简单的,因为期间发生的任何错误最多只影响到一个请求。
对应class SyncWorker(base.Worker) ,源码分析参考:https://github.com/benoitc/gunicorn/blob/master/gunicorn/workers/sync.py
这里指的是greenlet worker,异步workers分两种,一个是eventlet,另一个是gevent。二者都是基于greenlets软件包。
greenlet是用python来实现的协程方式实现的(cooperative multi-threading)。源码对应文件:geventlet.py和ggevent.py。
建议采用异步worker的场景:
1)需要长时间阻塞调用的应用,比如外部的web service
2)直接给internet提供服务
3)stream流请求和响应
4)长轮询
5)Web sockets(web sockets可以允许用户在浏览器中实现双向通信,实现数据的及时推送)
6)Comet彗星(基于HTTP长连接的服务器推送技术(Server Push),是一种新的 Web 应用架构。服务器端会主动以异步的方式向客户端程序推送数据。Comet架构适用于事件驱动的 Web 应用,以及对交互性和实时性要求很强的应用)
可以用于使用Tornado框架编写应用程序。不推荐使用这种配置。
此worker与Python3兼容。
gaiohttp worker利用aiohttp库实现异步I/O,支持web socket;
gthread worker采用的是线程工作模式,利用线程池管理连接。它在主循环中接受连接,已接受的连接作为连接作业添加到线程池中。
sync底层实作是每个请求都由一个process处理。多进程模式,--workers参数,指定了工作进程的数量。
gthread则是每个请求都由一个thread处理。多线程模式。
eventlet、gevent底层则是利用非同步IO让一个process在等待IO回应时继续处理下个请求。协程模式。
IO受限,建议使用gevent或者asyncio
CPU受限,建议增加workers数量
不确定内存占用?建议使用gthread
不知道怎么选择?建议增加workers数量
gunicorn可以启动多个worker子进程,每个子进程可以看做是一个独立的Flask进程。在处理请求时,gunicorn依靠操作系统来提供负载均衡。
预期多少个客户端,就启用多少个worker。gunicorn只需要启用4–12个workers,就足以每秒钟处理几百甚至上千个请求了。
推荐的worker数量是:(2 x $num_cores) + 1,这个公式很简单,它是基于给定的核心处理器数量,在其他worker处理请求时,每个worker将从socket那进行读写操作。
如果worker太多,会在某一个时刻发生系统颠簸,降低整个系统的吞吐量。
TTIN和TTOU信号可以发送给master来增加或减少worker的数量。
将worke数增加1:
$ kill -TTIN $masterpid
将worke数量减少1:
$ kill -TTOU $masterpid
操作示例:
[root@controller1 ~]# ps -ef| grep gunicorn
root 18290 6795 0 11:00 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 18303 18290 0 11:00 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 18304 18290 0 11:00 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 18306 18290 0 11:00 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 25038 12940 0 11:01 pts/6 00:00:00 grep --color=auto gunicorn
[root@controller1 ~]# kill -TTIN 18290
[root@controller1 ~]# kill -TTIN 18290
[root@controller1 ~]# ps -ef| grep gunicorn
root 18290 6795 0 11:00 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 18303 18290 0 11:00 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 18304 18290 0 11:00 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 18306 18290 0 11:00 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 25726 18290 0 11:01 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 27029 18290 4 11:01 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 27231 12940 0 11:01 pts/6 00:00:00 grep --color=auto gunicorn
[root@controller1 ~]# kill -TTOU 18290
[root@controller1 ~]# kill -TTOU 18290
[root@controller1 ~]# kill -TTOU 18290
[root@controller1 ~]# ps -ef| grep gunicorn
root 18290 6795 0 11:00 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 25726 18290 0 11:01 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 27029 18290 0 11:01 pts/1 00:00:00 /usr/bin/python /usr/bin/gunicorn -w 3 -b 127.0.0.1:5000 --chdir /home/monitor_project start_monitor:app -k eventlet
root 31435 12940 0 11:02 pts/6 00:00:00 grep --color=auto gunicorn
[root@controller1 ~]#
gunicorn允许每个worker拥有多个线程。
多线程模式下,每个worker都会加载一次,同一个worker生成的每个线程共享相同的内存空间。
使用threads模式,每一次使用threads模式,worker类就会是gthread。
执行如下:
# gunicorn -w 5 --threads=2 main:app
等同于:
# gunicorn -w 5 --thread=2 --worker-class=gthread main:app
以上所举示例,最大的并发请求就是worker*线程,也就是10。 worker建议的最大并发数是(2*CPU) +1
eventlet和gevent都是协程工作模式。
以下示例,使用gevent做协程,解决高并发的问题。
$ gunicorn --worker-class=gevent --worker-connections=1000 -w 3 main:app
work-connections 是对gevent worker类的特殊设置,当前配置的最大的并发请求数是3000(3个worker*1000连接)
建议workers最大数量仍是 (2*CPU) + 1
管理gunicorn服务,可以使用systemd和supervisord,这里使用linux自带的系统服务管理器Systemd,来管理gunicorn服务。
下面是使用systemd为传入的Gunicorn请求创建unix套接字的配置文件和说明。Systemd将监听这个套接字,并启动gunicorn自动响应流量。
/etc/systemd/system/gunicorn.service
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
Type=notify
# the specific user that our service will run as
User=someuser
Group=someuser
# another option for an even more restricted service is
# DynamicUser=yes
# see http://0pointer.net/blog/dynamic-users-with-systemd.html
RuntimeDirectory=gunicorn
WorkingDirectory=/home/someuser/applicationroot
ExecStart=/usr/bin/gunicorn applicationname.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
/etc/systemd/system/gunicorn.socket
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
# Our service won't need permissions for the socket, since it
# inherits the file descriptor by socket activation
# only the nginx daemon will need access to the socket
SocketUser=www-data
# Optionally restrict the socket permissions even more.
# SocketMode=600
[Install]
WantedBy=sockets.target
启动gunicorn.socket
systemctl enable --now gunicorn.socket
Flask原生和gunicorn并发,压测对比
直接用python执行monitor.py文件:
[root@controller1 ~]# python /home/monitor_project/test_app.py
* Running on http://127.0.0.1:6666/ (Press CTRL+C to quit)
[root@controller1 ~]# ab -n 500 -c 100 http://127.0.0.1:6666/todos
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests
Server Software: Werkzeug/1.0.1
Server Hostname: 127.0.0.1
Server Port: 6666
Document Path: /todos
Document Length: 152 bytes
Concurrency Level: 100
Time taken for tests: 0.571 seconds
Complete requests: 500
Failed requests: 0
Write errors: 0
Total transferred: 149000 bytes
HTML transferred: 76000 bytes
Requests per second: 876.02 [#/sec] (mean)
Time per request: 114.153 [ms] (mean)
Time per request: 1.142 [ms] (mean, across all concurrent requests)
Transfer rate: 254.93 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.4 0 5
Processing: 3 101 25.6 110 117
Waiting: 3 100 25.7 109 116
Total: 7 101 24.4 110 117
Percentage of the requests served within a certain time (ms)
50% 110
66% 111
75% 112
80% 113
90% 116
95% 116
98% 117
99% 117
100% 117 (longest request)
[root@controller1 ~]#
可看出 Requests per second为 876
[root@controller1 ~]# gunicorn -w 12 -b 127.0.0.1:6666 --chdir /home/myproject test_app:app -k eventlet
[2021-01-22 11:38:34 +0000] [10695] [INFO] Starting gunicorn 19.10.0
[2021-01-22 11:38:34 +0000] [10695] [INFO] Listening at: http://127.0.0.1:6666 (10695)
[2021-01-22 11:38:34 +0000] [10695] [INFO] Using worker: eventlet
使用ab进行压力测试:
[root@controller1 ~]# ab -n 500 -c 100 http://127.0.0.1:6666/todos
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests
Server Software: gunicorn/19.10.0
Server Hostname: 127.0.0.1
Server Port: 6666
Document Path: /todos
Document Length: 94 bytes
Concurrency Level: 100
Time taken for tests: 0.081 seconds
Complete requests: 500
Failed requests: 0
Write errors: 0
Total transferred: 123500 bytes
HTML transferred: 47000 bytes
Requests per second: 6186.28 [#/sec] (mean)
Time per request: 16.165 [ms] (mean)
Time per request: 0.162 [ms] (mean, across all concurrent requests)
Transfer rate: 1492.20 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.5 0 6
Processing: 1 10 9.2 8 73
Waiting: 1 10 9.2 8 73
Total: 1 11 9.2 9 77
Percentage of the requests served within a certain time (ms)
50% 9
66% 12
75% 14
80% 15
90% 21
95% 30
98% 40
99% 44
100% 77 (longest request)
[root@controller1 ~]#
对比可看出,单个程序运行和使用gunicorn,吞吐率相差很多。
可看出 Requests per second为 6186.28,吞吐性能提高了8倍。
[root@controller1 ~]# ab -n 5000 -c 1 http://127.0.0.1:6666/todos
单进程吞吐率:
Requests per second: 876.02 [#/sec] (mean)
gunicorn多进程吞吐率:
Requests per second: 865.62 [#/sec] (mean)
Server Hostname: 请求的URL中的主机部分名称,它来自于http请求数据的头信息。
Server Port: 被测试Web服务器监听端口。
Document Path: 请求的URL中根绝对路径,它同样来自于http请求数据的头信息,通过它的后缀名,我们一般可以理解该请求的类型。
Document Length: http响应数据的正文长度。
Concurrency Level: 并发用户数,由命令行的-c设置的参数。
Time taken for tests: 所有请求被处理完成花费的总时间。
Complete requests: 总请求数,由命令行-n设置的参数。
Failed requests: 失败的请求数。
Total transferred: 所有请求响应数据长度总和。不包括http请求数据的长度,使用ab的-v参数可查看详细的http头信息。
HTML transferred: 所有请求的响应数据中正文数据的总和,也就是减去了Total transferred中http响应数据中头信息的长度。
Requests per second:【重点关注】吞吐率,计算方法:Complete requests / Time taken for tests = Requests per second
Time per request:用户平均请求等待时间,计算方法: Time per request = Time taken for tests / (Complete requests /Concurrency Level)
Time per request(across all concurrent requests): 服务器平均请求处理时间,计算方法: Time per request(across all concurrent requests) = Time taken for tests / Complete requests
Time per request(across all concurrent requests) = 1 / Requests per second
Time per request(across all concurrent requests) = Time per request / Concurrency Level
Transfer rate :请求在单位时间内从服务器获取的数据长度,计算方法: Transfer rate = Total transferred / Time taken for tests 。这个统计项可以很好的说明服务器在处理能力达到限制时,其出口带宽的需求量。
Percentage of the requests served within a certain time(ms): 描述每个请求处理时间的分布情况。
Gevent是一个基于greenlet的Python的并发框架,以微线程greenlet为核心,使用了epoll事件监听机制以及诸多其他优化而变得高效。
与greenlet、eventlet相比,Gevent性能略低,但是它封装的API非常完善,还提供了一个monkey类,可以将现有基于Python线程直接转化为greenlet。
eventlet是一款使用Python编写的为高并发的网络编程而设计的库。由第二人生(secondlife)所开源发布。在开源云计算技术OpenStack里起到了比较重要的作用。
它通过greenlet提供的协程功能,让开发者可以不用将以往的多线程等并发程序的开发方式转变成异步状态机模型,就能直接使用select/epoll/kqueue等操作系统提供的支持高并发IO接口,并且能尽可能地发挥它们在并发上的优势。
与它同类的另一款产品是Gevent,它们有着很类似的设计。
在CPython下,由于Gevent使用了Cython绑定了libev或者libevent等C库,导致Gevent比eventlet有着更优秀的性能。
但是也因为Cython写的部分组件,导致Gevent无法借助PyPy来给它加速,而eventlet则没有这个限制。在PyPy的加速下,eventlet的性能可以有成倍的提升。
参考:
https://dormousehole.readthedocs.io/en/latest/quickstart.html
https://docs.gunicorn.org/en/latest/run.html
https://gunicorn.readthedocs.io/en/latest/