41-Gunicorn 源码解析
- gunicron 官网:https://docs.gunicorn.org/en/stable/
- github 地址:https://github.com/benoitc/gunicorn
总体结构
Gunicorn 使用 pre-fork worker model
- worker model:意味着这个模型有一个 master 进程,来管理一组 worker进程;
- fork:意味着 worker 进程是由 master 进程 fork(复刻)出来的;
- pre-:意味着在任何客户端请求到来之前,就已从 master 进程 fork 出了多个 worker 进程,坐等请求到来;
在处理请求之前,master 会预先 fork 出多个 worker 进程。每个 worker 进程都是独立的,可以在不同的 CPU 核心上运行。
- Master 不处理 HTTP 请求,只用来管理这些子进程。比如对 worker 进程的创建、销毁、以及根据负载情况增减、升级、重载配置、kill 进程避免 OOM 等。
- worker 进程的事件循环就是监听网络事件并处理(如新建连接,断开连接,处理请求发送响应等等)。worker 继承了主进程的 listening fd,从 accept、parse http protocol、response 都是在一个 gevent 协程里面的,也就说在协程池的数目允许下,每个连接就是一个 gevent 协程。
执行流程:在 worker 进程创建时,就被实例化了 Python web app;并由 worker 进程监听端口、处理请求。当请求到来时,worker 进程就能解析 HTTP 请求、调用 Python web app 处理、得到处理结果后,再整理成 HTTP Response,通过 TCP 返回给客户端。
启动时设置的 workers 参数只是 worker 数,而 Gunicorn 还会创建个 master 进程。所以,即使配置 workers 为 1,你的 app 也至少有两个进程。
这种模型的优点在于:
- 隔离性好:每个 worker 进程都是独立的,如果一个进程崩溃,不会影响其他进程。
- 扩展性强:可以根据需要增加或减少worker进程的数量。
- 性能优越:可以充分利用多核CPU的并行处理能力。
(以下代码中为了方面理解,均去除了一些干扰代码)。
启动之后,manager 维护数量固定的 worker:
1 |
|
master 通过 pre-fork 的方式创建多个 worker:
1 |
|
在 worker.init_process()
函数中,worker 中 Gunicorn 的 app 对象会去 import 我们的 wsgi app。也就是说,每个 woker 子进程都会单独去实例化我们的 wsgi app 对象。每个 worker 中的 swgi app 对象是相互独立、互不干扰的。
创建完所有的 worker 后,worker 和 master 各自进入自己的消息循环。
worker
woker 有很多种,包括:ggevent、geventlet、gtornado、gthread 等等。这里主要分析 ggevent。
每个 ggevent worker 启动的时候会启动多个 server 对象:worker 首先为每个 listener 创建一个 server 对象(注为什么是一组 listener,因为 Gunicorn 可以绑定一组地址,每个地址对应一个 listener),每个 server 对象都有运行在一个单独的 gevent pool 对象中。真正等待链接和处理链接的操作是在 server 对象中进行的。
1 |
|
上面代码中的 server_class
实际上是一个 gevent 的 WSGI SERVER 的子类:
1 |
|
WSGI SERVER
真正等待链接和处理链接的操作是在 gevent 的 WSGIServer 和 WSGIHandler 中进行的。
最后再来看一下 gevent 的 WSGIServer 和 WSGIHandler 的主要实现:
WSGIServer 的 start 函数里面调用 start_accepting
来处理到来的链接。在 start_accepting
里面得到接收到的套接字后调用 do_handle
来处理套接字:
1 |
|
可以看出,WSGIServer 实际上是创建一个协程去处理该套接字,也就是说在 WSGIServer 中,一个协程单独负责一个 HTTP 链接。协程中运行的 self._handle
函数实际上是调用了 WSGIHandler 的 handle 函数来不断处理 HTTP 请求:
1 |
|
在 handle 函数的循环内部,handle_one_request
函数首先读取 HTTP 请求,初始化 WSGI 环境,然后最终调用 run_application
函数来处理请求:
1 |
|
在这个地方才真正的调用了我们的 app。
总结:Gunicorn 会启动一组 worker 进程,所有 worker 进程公用一组 listener,在每个 worker 中为每个 listener 建立一个 wsgi server。每当有 HTTP 链接到来时,wsgi server 创建一个协程来处理该链接,协程处理该链接的时候,先初始化 WSGI 环境,然后调用用户提供的 app 对象去处理 HTTP 请求。
accept 惊群
在内核 2.6 就没有 accept 惊群这个问题了,
但是当我们多个进程各自把 listen fd 放到 epoll 监听池里面时,其实会造成事件的唤醒,虽然最终只会被一次 accept,但也会有惊群的现象产生。
- nginx 是通过多个进程轮流持锁的方式来避免 epoll accept 唤醒问题。