Python项目高并发异步部署实战+压测
前言
由于Python有把大锁GIL,会将多个线程在同一时刻,只能有一个线程执行,变成'串行',所以一个多线程python进程,并不能充分使用多核CPU资源,所以对于Python进程,可采用多进程部署方式比较有利于充分利用多核的CPU资源,而uWSGI服务器就是这么一个东西,可以以多进程方式执行WSGI app,其工作模式为 1 master进程 + N worker进程+m个线程(N*m线程),主进程负责接收客户端请求,然后将请求转发给worker进程,因此最终是worker进程负责处理客户端请求,这样很方便的将WSGI app以多进程方式进行部署此方式是企业常用部署方式
但是存在一个问题
客户端向uwsgi的一个master发起一个HTTP请求,master分发给不同的worker进程,worker进程拉起线程处理请求,进入web框架,直到该请求处理完,这个线程才能处理其他请求,所以wsgi app是同步的。假如一个请求处理花费时间比较久,客户端请求数量又比较多的情况下,所有的线程都会被占满,所以就处理不了更多的请求(最大连接数取决于进程N*线程M)
我们的处理方案
1)增大N,即worker的数量:在增加进程的数量的时候,进程是要消耗内存的,并且如果进程数量太多的情况下(并且进程均处于活跃状态),进程间的切换会消耗系统资源的,所以N并不是越大越好。一般情况下,可能将进程数目设置为CPU数量的2倍
2)增大m,即worker的线程数量:线程创建也是有限度的,由于线程栈是要消耗内存的,线程数量太多也不行
于是我们想,一条线程能不能同时处理多个请求呢?可以使用IO多路复用的模型
于是,gevent登场了
gevent是用户态的'线程',也就是协程,单线程下实现并发
gevent的好处就是无需等待I/O,当发生I/O调用是,gevent会主动切换到另一gevent进行运行,这样可以充分利用CPU资源
同时gevent通过monkey patch(猴子补丁)替换掉了标准库中阻塞的模块
以flask项目为例,使用uwsgi+gevent部署高并发实战
1、新建flask项目 (s1.py)
from flask import Flask import time app = Flask(__name__) @app.route('/') def hello_world(): time.sleep(1) # 引入io操作 return 'Hello World!' if __name__ == '__main__': app.run()
2、新建uwsgi.py
[uwsgi] http = 0.0.0.0:5000 chdir = /root/20210318/ wsgi-file = /root/20210318/s1.py processes = 2 threads = 8 buffer-size = 32768 master = true pidfile = uwsgi.pid daemonize = uwsgi.log callable = app --------------------------- [uwsgi] http = 0.0.0.0:5000 # 监听地址和端口 chdir = /root/test/ # 项目路径 wsgi-file = /root/test/s1.py # py文件路径 processes = 1 # 进程数 threads = 1 # 线程数 buffer-size = 32768 # 缓存大小 master = true #允许主进程存在 pidfile = uwsgi.pid # pid位置 daemonize = uwsgi.log#日志文件 callable = app #flask中flask对象的名字,flask部署需要配置它 -----------uwsgi其他参数------------------ socket : 地址和端口号,例如:socket = 127.0.0.1:50000 processes : 开启的进程数量 workers : 开启的进程数量,等同于processes(官网的说法是spawn the specified number of workers / processes) chdir : 指定运行目录,项目根路径(chdir to specified directory before apps loading) wsgi-file : 载入wsgi-file,wsgi文件,flask就是app.py(load .wsgi file) stats : 在指定的地址上,开启状态服务(enable the stats server on the specified address) threads : 运行线程。(run each worker in prethreaded mode with the specified number of threads) master : 允许主进程存在(enable master process) daemonize : 使进程在后台运行,并将日志打到指定的日志文件或者udp服务器(daemonize uWSGI)。实际上最常用的,还是把运行记录输出到一个本地文件上。 log-maxsize :以固定的文件大小(单位KB),切割日志文件。 例如:log-maxsize = 50000000 就是50M一个日志文件。 pidfile : 指定pid文件的位置,记录主进程的pid号。 vacuum : 当服务器退出的时候自动清理环境,删除unix socket文件和pid文件(try to remove all of the generated file/sockets) disable-logging : 不记录请求信息的日志。只记录错误以及uWSGI内部消息到日志中。如果不开启这项,那么你的日志中会大量出现这种记录: log-maxsize : 日志大小,当大于这个大小会进行切分 (Byte) log-truncate : 当启动时切分日志 buffer-size : 比如前端(客户端)向后端(服务器)发了一个请求,请求的大小是5M,那么buffer-size的大小就得大于1024*5,不然就报错了 callable :app #变量app 与 App.py文件中的app = Flask(__name__)对应
3、在centos7.5机器上测试
第一步:安装python3.6环境(略) 第二步:安装uwsgi (pip3 install uwsgi) -uwsgi在1.4版本以上,就支持gevent(官方介绍:https://uwsgi-docs-zh.readthedocs.io/zh_CN/latest/Gevent.html) 第三步:安装flask (pip3 install flask) 第四步:安装gevent (pip3 install gevent)
4、首先不使用gevent启动uwsgi
uwsgi uwsgi.ini
5、使用gevent启动uwsgi
uwsgi --gevent 100 --gevent-monkey-patch uwsgi.ini
6、压测两个接口
ab -n 10 -c 5 http://101.133.225.166:5000/ 使用gevent跑,可以看到 requests per second (每秒请求数为3.28)
ab -n 10 -c 5 http://101.133.225.166:5000/ 不使用gevent跑,可以看到 requests per second (每秒请求数为0.99)