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在处理报文了)

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