## 1 前言
由于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(猴子补丁)替换掉了标准库中阻塞的模块
## 2 以flask项目为例,使用uwsgi+gevent部署高并发实战
### 2.1 新建flask项目 (s1.py)
```python
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.2 新建uwsgi.py
```python
[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__)对应
```
## 2.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)
## 2.4 首先不使用gevent启动uwsgi
>uwsgi uwsgi.ini
## 2.5 使用gevent启动uwsgi
>
>uwsgi --gevent 100 --gevent-monkey-patch uwsgi.ini
>
## 2.6 压测两个接口
>ab -n 10 -c 5 http://101.133.225.166:5000/
>
>不使用gevent跑,可以看到 requests per second (每秒请求数为0.99)
>ab -n 10 -c 5 http://101.133.225.166:5000/
>
>使用gevent跑,可以看到 requests per second (每秒请求数为3.28)