## 1 前言
由于Python有把大鎖GIL,會將多個線程在同一時刻,只能有一個線程執(zhí)行,變成'串行',所以一個多線程python進(jìn)程,并不能充分使用多核CPU資源,所以對于Python進(jìn)程,可采用多進(jìn)程部署方式比較有利于充分利用多核的CPU資源,而uWSGI服務(wù)器就是這么一個東西,可以以多進(jìn)程方式執(zhí)行WSGI app,其工作模式為 1 master進(jìn)程 + N worker進(jìn)程+m個線程(N*m線程),主進(jìn)程負(fù)責(zé)接收客戶端請求,然后將請求轉(zhuǎn)發(fā)給worker進(jìn)程,因此最終是worker進(jìn)程負(fù)責(zé)處理客戶端請求,這樣很方便的將WSGI app以多進(jìn)程方式進(jìn)行部署**此方式是企業(yè)常用部署方式**
**但是存在一個問題**
客戶端向uwsgi的一個master發(fā)起一個HTTP請求,master分發(fā)給不同的worker進(jìn)程,worker進(jìn)程拉起線程處理請求,進(jìn)入web框架,直到該請求處理完,這個線程才能處理其他請求,所以wsgi app是同步的。假如一個請求處理花費時間比較久,客戶端請求數(shù)量又比較多的情況下,所有的線程都會被占滿,所以就處理不了更多的請求(最大連接數(shù)取決于進(jìn)程N(yùn)*線程M)
**我們的處理方案**
1)增大N,即worker的數(shù)量:在增加進(jìn)程的數(shù)量的時候,進(jìn)程是要消耗內(nèi)存的,并且如果進(jìn)程數(shù)量太多的情況下(并且進(jìn)程均處于活躍狀態(tài)),進(jìn)程間的切換會消耗系統(tǒng)資源的,所以N并不是越大越好。一般情況下,可能將進(jìn)程數(shù)目設(shè)置為CPU數(shù)量的2倍
2)增大m,即worker的線程數(shù)量:線程創(chuàng)建也是有限度的,由于線程棧是要消耗內(nèi)存的,線程數(shù)量太多也不行
于是我們想,一條線程能不能同時處理多個請求呢?可以使用IO多路復(fù)用的模型
**于是,gevent登場了**
gevent是用戶態(tài)的'線程',也就是協(xié)程,單線程下實現(xiàn)并發(fā)
gevent的好處就是無需等待I/O,當(dāng)發(fā)生I/O調(diào)用是,gevent會主動切換到另一gevent進(jìn)行運行,這樣可以充分利用CPU資源
同時gevent通過monkey patch(猴子補(bǔ)?。┨鎿Q掉了標(biāo)準(zhǔn)庫中阻塞的模塊
## 2 以flask項目為例,使用uwsgi+gevent部署高并發(fā)實戰(zhàn)
### 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 # 監(jiān)聽地址和端口
chdir = /root/test/ # 項目路徑
wsgi-file = /root/test/s1.py # py文件路徑
processes = 1 # 進(jìn)程數(shù)
threads = 1???# 線程數(shù)
buffer-size = 32768 # 緩存大小
master = true???????#允許主進(jìn)程存在
pidfile = uwsgi.pid # pid位置
daemonize = uwsgi.log#日志文件
callable = app???#flask中flask對象的名字,flask部署需要配置它
-----------uwsgi其他參數(shù)------------------
socket : 地址和端口號,例如:socket = 127.0.0.1:50000
processes : 開啟的進(jìn)程數(shù)量
workers : 開啟的進(jìn)程數(shù)量,等同于processes(官網(wǎng)的說法是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 : 在指定的地址上,開啟狀態(tài)服務(wù)(enable the stats server on the specified address)
threads : 運行線程。(run each worker in prethreaded mode with the specified number of threads)
master : 允許主進(jìn)程存在(enable master process)
daemonize : 使進(jìn)程在后臺運行,并將日志打到指定的日志文件或者udp服務(wù)器(daemonize uWSGI)。實際上最常用的,還是把運行記錄輸出到一個本地文件上。
log-maxsize :以固定的文件大小(單位KB),切割日志文件。 例如:log-maxsize = 50000000??就是50M一個日志文件。
pidfile : 指定pid文件的位置,記錄主進(jìn)程的pid號。
vacuum : 當(dāng)服務(wù)器退出的時候自動清理環(huán)境,刪除unix socket文件和pid文件(try to remove all of the generated file/sockets)
disable-logging : 不記錄請求信息的日志。只記錄錯誤以及uWSGI內(nèi)部消息到日志中。如果不開啟這項,那么你的日志中會大量出現(xiàn)這種記錄:
log-maxsize : 日志大小,當(dāng)大于這個大小會進(jìn)行切分 (Byte)
log-truncate : 當(dāng)啟動時切分日志
buffer-size : 比如前端(客戶端)向后端(服務(wù)器)發(fā)了一個請求,請求的大小是5M,那么buffer-size的大小就得大于1024*5,不然就報錯了
callable :app??#變量app 與 App.py文件中的app = Flask(__name__)對應(yīng)
```
## 2.3 在centos7.5機(jī)器上測試
>第一步:安裝python3.6環(huán)境(略)
>
>第二步:安裝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 (每秒請求數(shù)為0.99)
>ab -n 10 -c 5 http://101.133.225.166:5000/
>
>使用gevent跑,可以看到 requests per second (每秒請求數(shù)為3.28)