Gunicorn 0.2 源碼閱讀

安裝相關包

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在處理報文了)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章