安裝相關包
Flask==0.1
gunicorn==0.2
Jinja2==2.11.3
MarkupSafe==1.1.1
Werkzeug==0.6.1
測試代碼
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
注:以下所說子進程一般代指worker工作進程
gunicorn採用了pre-fork模式的設計, 主進程+多個子進程,主進程負責管理子進程,子進程負責HTTP請求的處理. 子進程的處理HTTP對父進程是不可見的.
在程序運行, 主進程會一直循環運行, 在循環之前就已經創建好了子進程, 循環期間檢測外部信號和檢查子進程健康情況, 從而作出對應處理. 主進程會監聽信號,例如, 當收到終止信號, 會將一個個子進程殺死. 殺死子進程分爲優雅停止和直接殺死. 優雅停止子進程能夠讓當前請求處理完再結束, 新的在等待(sock.accept)的請求將不會開始.
子進程會不斷從監聽的sock連接中,獲取client客戶端, 當得到一個client, 就會一直讀取http數據流數據,直到一個完整的http request報文數據獲取到,就會構造出wsgi協議需要的數據, 調用wsgi application(Flask等), 然後就得到了http response, 再通過client sock將response數據send出去.
gunicorn中的同步worker是隻能每個請求過來,處理完成就關閉(關閉的是client而不是listen),所以不管http keep-alive是否是1.1, 都無法生效(使用異步worker支持).
子進程是否共用一個tcp監聽連接?
是的(子進程從主進程複製了一份), 主進程創建了TCP連接, 並且在子進程創建的時候傳遞給了子進程
worker = Worker(i, self.pid, self.LISTENER, self.modname,
self.timeout)
殺死主進程的流程
通常主進程會阻塞在sleep中, gunicorn通過管道實現了休眠等待信號的邏輯, 當按下ctrl+c
會走到except select.error, e:
的邏輯, EINTR: 終止信號影響了select.select
def sleep(self):
try:
ready = select.select([self.PIPE[0]], [], [], 100.0)
if not ready[0]:
return
while os.read(self.PIPE[0], 1):
pass
except select.error, e:
if e[0] not in [errno.EAGAIN, errno.EINTR]:
raise
except OSError, e:
if e.errno not in [errno.EAGAIN, errno.EINTR]:
raise
except KeyboardInterrupt:
print 3
sys.exit()
當跳出sleep, 就會執行對應信號的處理邏輯, 如果是停止服務, 就會去遍歷當前所有子進程, 通過子進程的id去kill掉
def kill_workers(self, sig):
for pid in self.WORKERS.keys():
self.kill_worker(pid, sig)
def kill_worker(self, pid, sig):
worker = self.WORKERS.pop(pid)
try:
os.kill(pid, sig)
kpid, stat = os.waitpid(pid, os.WNOHANG)
if kpid:
self.log.warning("Problem killing process: %s" % pid)
except OSError, e:
if e.errno == errno.ESRCH:
try:
worker.tmp.close()
except:
pass
主進程如何檢查子進程的健康情況
子進程通過不斷一個修改一個自己的臨時文件的inode信息, 主進程在循環中不斷檢查這個inode信息的stat.st_ctime(i節點最後更改時間)來判斷子進程是否正常.
解析HTTP報文是gunicorn做還是Flask做?
HTTP報文詳細的由Flask解析, 通過wsgi的wsgi_input流數據解析. gunicorn解析了一些基礎信息
environ = {
"wsgi.url_scheme": 'http',
"wsgi.input": wsgi_input,
"wsgi.errors": sys.stderr,
"wsgi.version": (1, 0),
"wsgi.multithread": False,
"wsgi.multiprocess": True,
"wsgi.run_once": False,
"SCRIPT_NAME": "",
"SERVER_SOFTWARE": self.SERVER_VERSION,
"REQUEST_METHOD": self.parser.method,
"PATH_INFO": unquote(self.parser.path),
"QUERY_STRING": self.parser.query_string,
"RAW_URI": self.parser.raw_path,
"CONTENT_TYPE": self.parser.headers_dict.get('Content-Type', ''),
"CONTENT_LENGTH": str(wsgi_input.len),
"REMOTE_ADDR": self.client_address[0],
"REMOTE_PORT": self.client_address[1],
"SERVER_NAME": self.server_address[0],
"SERVER_PORT": self.server_address[1],
"SERVER_PROTOCOL": self.parser.raw_version
}
多個子進程一起去accept客戶端client會怎樣?
在舊的內核中會產生多個子進程同時喚醒(驚羣問題), 新的內核只有一個能得到client.
假設A B C三子進程同時在select.select
, 那麼只會有其中一個返回, 例如是B, 現在的等待隊列之後A C(因爲B在處理報文了)