一,獨角獸
Gunicorn 服務器作為wsgi app的容器, 采用 pre-fork 模型中有一個管理進程以及幾個的工作進程。master 管理多個 slave 進程
創建slave進程,監聽事件:
1, 根據定義的 work數量 創建多個 work 進程
2, 在worker.init_process()函數中,每個woker子進程都會單獨去實例化我們的wsgi app對象。針對多進程,一個進程實例化一個 app 對象,多線程,一個線程處理實例化app對象,協程根據server對象
3, 然后,worker和master各自進入自己的消息循環。 master的事件循環就是收收信號,管理管理worker進程,
而worker進程的事件循環就是監聽網絡事件並處理(如新建連接,斷開連接,處理請求發送響應等等),真正的連接最終是連到了worker進程上的
# 創建 固定數量的 worker(由 manager 進行維護)
def manage_workers(self):
if len(self.WORKERS.keys()) < self.num_workers:
self.spawn_workers()
while len(workers) > self.num_workers:
(pid, _) = workers.pop(0)
self.kill_worker(pid, signal.SIGQUIT)
# 創建 work 進程
def spawn_worker(self):
self.worker_age += 1
#創建worker。請注意這里的app 對象並不是真正的wsgi app對象,而是gunicorn的app
#對象。gunicorn的app對象負責import我們自己寫的wsgi app對象。
worker = self.worker_class(self.worker_age, self.pid, self.LISTENERS,
self.app, self.timeout / 2.0,
self.cfg, self.log)
# pid = 0 表示子進程
pid = os.fork()
#父進程,返回后繼續創建其他worker,沒worker后進入到自己的消息循環
if pid != 0:
# 父進程記錄子進程
self.WORKERS[pid] = worker
return pid
# 子進程繼續運行
# Process Child
worker_pid = os.getpid()
try:
..........
worker.init_process() #子進程,初始化woker,進入worker的消息循環,
sys.exit(0)
except SystemExit:
raise
............
View Code
二, Gunicorn 幾種啟動方式 sync (默認值) eventlet gevent tornado
IO 受限 -建議使用gevent或者asyncio
CPU受限 -建議增加workers數量
不確定內存占用? -建議使用gthread
# gunicorn 啟動4個進程(默認啟動方式),每個 work 單線程 並發量 4*1
gunicorn -w 4 -b 0.0.0.0:8000 demo:app
# gunicorn 允許每個worker擁有多個線程 並發量 = 4*2
gunicorn -w 4 --thread=2 --worker-class=gthread main:app
# gunicorn 偽線程 gevent (協程) 並發量 3*1000
gunicorn --worker-class=gevent --worker-connections=1000 -w 3 main:app
三, web 異步任務實現方式:
1, web 的多並發 並不是有 flask 來實現的, flask 只是負責根據Request產生Response(一個python程序),
多並發是由 WSGI server是通過進程(pre-fork)來並發的。這樣並發就取決與進程數,如果WSGI server用了gevent,
eventlet等 green thread技術,就可以支持更多並發
2, 針對 work 啟動方式 gevent
每個ggevent worker啟動的時候會啟動多個server對象,worker首先為每個listener創建一個server對象,
每個server對象都有運行在一個單獨的gevent pool對象中。真正等待鏈接和處理鏈接的操作是在server對象中進行的
3,WSGIServer 實際上是創建一個協程去處理該套接字,也就是說在WSGIServer 中,一個協程單獨負責一個HTTP鏈接。
協程中運行的self._handle函數實際上是調用了WSGIHandler的handle函數來不斷處理http 請求
4,gunicorn 會啟動一組 worker進程,所有worker進程公用一組listener,在每個worker中為每個listener建立一個wsgi server。
每當有HTTP鏈接到來時,wsgi server創建一個協程來處理該鏈接,協程處理該鏈接的時候,先初始化WSGI環境,
然后調用用戶提供的app對象去處理HTTP請求(處理http請求可以理解為一個程序)。
#為每個listener創建server對象。
for s in self.sockets:
pool = Pool(self.worker_connections) #創建gevent pool
if self.server_class is not None:
#創建server對象
server = self.server_class(
s, application=self.wsgi, spawn=pool, log=self.log,
handler_class=self.wsgi_handler, **ssl_args)
.............
server.start() #啟動server,開始等待鏈接,服務鏈接
servers.append(server)
.........
View Code
在handle函數的循環內部,handle_one_request函數首先讀取HTTP 請求,初始化WSGI環境,然后最終調用run_application函數來處理請求
def run_application(self):
self.result = self.application(self.environ, self.start_response)
self.process_result()