[关闭]
@orangleliu 2016-07-13T15:13:27.000000Z 字数 3458 阅读 2701

【Gunicorn】源码阅读笔记

源码阅读 gunicorn


源码分析的一个项目,之前基本都是浏览各种源码,对其中的细节看的少,没有总结,也没有更高层次的归纳,所以这次认真的来分析一个Py项目的源码。 gunicorn是python的一个http服务器,来看看怎样设计和实现一个HTTP服务器。

目标

没有任何目的的研究可能也收获不了什么。

阅读时候在源码上加了一些注释,放到github gunicorn-souce-reading

参考资料:

设计思想

我这里是 git clone 出来源码之后,直接使用软连接替换 virtualenv 中的gunicorn源码来实现调试的。

源码结构

这里的版本是19.45。 源码包中包含了 docs(文档),examples(案例),tests(测试)以及 gunicorn(py源码)还有一些安装脚本,说明信息等。

py源码的目录结构。

  1. gunicorn
  2. ├── __init__.py
  3. ├── _compat.py
  4. ├── app
  5. ├── __init__.py
  6. ├── base.py
  7. ├── django_wsgi.py
  8. ├── djangoapp.py
  9. ├── pasterapp.py
  10. └── wsgiapp.py
  11. ├── arbiter.py
  12. ├── argparse_compat.py
  13. ├── config.py
  14. ├── debug.py
  15. ├── errors.py
  16. ├── glogging.py
  17. ├── http
  18. ├── __init__.py
  19. ├── _sendfile.py
  20. ├── body.py
  21. ├── errors.py
  22. ├── message.py
  23. ├── parser.py
  24. ├── unreader.py
  25. └── wsgi.py
  26. ├── instrument
  27. ├── __init__.py
  28. └── statsd.py
  29. ├── management
  30. ├── __init__.py
  31. └── commands
  32. ├── __init__.py
  33. └── run_gunicorn.py
  34. ├── pidfile.py
  35. ├── reloader.py
  36. ├── selectors.py
  37. ├── six.py
  38. ├── sock.py
  39. ├── util.py
  40. └── workers
  41. ├── __init__.py
  42. ├── _gaiohttp.py
  43. ├── async.py
  44. ├── base.py
  45. ├── gaiohttp.py
  46. ├── geventlet.py
  47. ├── ggevent.py
  48. ├── gthread.py
  49. ├── gtornado.py
  50. ├── sync.py
  51. └── workertmp.py

Master-workers

这模式从早期的版本就有了,github上最早能看到的tag是0.2, 那个版本比较容易理解,worker是同步模式,功能相对少一些。7.0版本开始添加 async的支持,worker可以有多种选择。

核心是arbiter.py文件,通过信号来控制进程的行为,需要一些操作系统和系统编程的知识才比较好理解。

涉及到的信号

疑惑

通读了这段代码之后,pipe 管道和 signal 信号相互怎么作用的一直没明白。 为什么要用 pipe的读写来唤醒和休眠进程? 信号发出之后怎样添加到signal queue中的?

想法: 然后我就把所有的wakeup和sleep的代码都注释掉,发现程序运行的好好的,就是说整个master的loop频率非常快。 这个时候把 sleep开启(wakeup仍然注释),调整的5秒,再次测试的时候发现loop变成5s了,使用Ctrl+c来停止进程,有时候甚至是无法响应的,有时候可以接受到信号。 再接着把wakeup打开,sleep调整到5s, Ctrl+c就能即时响应了,loop时间还是5s左右。 所以猜测这里用pipe读写+select 来减少master loop的资源消耗,并仍能即使响应系统信号。

arbiter.py 执行流程

  1. __ini__() 初始化各种参数,包括设置app,app的配置
  2. run() 开始进程
  3. start() 写入pid
  4. init_signals() 初始化所有信号的handler,重新创建pipe
  5. create_sockets 创建监听的socket
  6. worker_class 是否要配置不同的worker
  7. 第一次初始化所有子进程
  8. 进入master的循环
    • 检查worker是否一致
    • 检查是否有新的信号 (如果有信号到来,就根据不同信号给不同的handler来处理)

不同worker

worker进程才是主要干活的工人,之前就说gunicorn提供了多个类型的worker,来看看他们是怎么实现的?代码在workers目录下。 worker的主要功能:
1. 对信号的处理
2. 接受socket内容,解析成http,遵循wsgi调用返回http响应

这里是其他worker的父类,定义了接口和公共方法(进程处理相关的居多) ,各种worker在会具体的实现这些接口。

默认的是一种同步的worker,也是最简单和最早的类型。

大概工作的流程:

  1. 子进程被fork出来之后,会调用 worker 对象的 init_process()方法 (base.py中定义和实现)
  2. 根据配置检查是否需要重新载入配置,如果有配置会reload配置
  3. 检查配置中是否有env变量需要设置,如果有配置env变量
  4. 设置子进程的用户组和用户
  5. 重新设置随机种子 util.seed()
  6. 新建一个pipe 并初始化
  7. 处理socket文件描述符 util.close_on_exec() 防止文件描述符继承
  8. 初始化信号的handler self.init_signals()
  9. 执行 post_worker_init 加载 server hook
  10. 加载wsgi模块 self.load_wsgi()
  11. 调用run() (sync.py)
    • 设置socket为nonblocking
    • 根据socket个数进行单个socket(run_for_one)或者多个socket的监听 (run_for_multiple)
    • 后面的逻辑就是不听的 loop,检查自己进程和父进程都alive的情况下,不断接受和处理请求 self.accept(), 使用的是select 来处理事件循环。

怎样从conn和handle 变成 reqeust和response的呢?

如果想使用基于gevent的worker使用 -k 参数 gunicorn test:app -k gevent

继承关系 base.Worker -> AsyncWorker -> GeventWorker

多线程的worker,依赖futures模块,使用线程来处理每个连接,用到了线程池。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注