gunicorn 原理

gunicorn

Gunicorn – unix 上被广泛使用的高性能 Python WSGI UNIX HTTP Server,常用于生产环境中运行的 Python Web 应用程序。

它所在的位置通常是在反向代理(Nginx)或者 负载均衡 和一个 web 应用之间。它是一个移植自 Ruby 的 Unicorn 项目的 pre-woker 模型,即支持 eventlet 也支持 greenlet

工作原理

Gunicorn 基于 pre-fork worker 模型。这意味着有一个中央主进程管理一组工作进程。

主进程是一个简单的循环,它监听各种进程信号并做出相应的反应。它通过监听 TTIN、TTOUT 和 CHLD 等信号来管理正在运行的工作人员列表。TTIN 和 TTOUT 告诉 master 增加或减少正在运行的 worker 的数量。CHLD 表示一个子进程已经终止,在这种情况下主进程会自动重启失败的工作进程。

特点

  • WSGI 兼容性:Gnicorn 符合 WSGI 规范,定义了 Web 服务器和 Python 应用程序之间通信的标准接口。这使得 Gunicorn 与许多 Python Web 框架(如 Django\Flask等)兼容。
  • 高性能:gunicorn 使用异步的、事件驱动的编程模型,具备大量的并发连接。采用 pre-fork 的方式,每个进程可以处理多个请求,并在请求到达时快速响应。
  • 丰富的配置:gunicorn 提供了丰富的配置选项、可以通过配置文件或者命令行参数进行调整。你可以指定工作进的数量、绑定的主机和端口、日志级别等。
  • 进程管理:gunicorn 具有内置的进程管理功能,可以管理工作进程的生命周期。它可以自动重启崩溃的进程,并在需要时平滑地进行热部署,而不会中断正在处理的请求。
  • 安全性:Gunicorn 提供了一些安全机制,包括 Unix 权限控制、访问日志记录和访问限制。它还可以与反向代理(Nginx)结合使用,以增强安全性和性能
  • 插件生态系统:Gunicorn 支持多插件,可以扩展其功能。例如,有插件用于加载静态文件、实现 SSL 加密、处理 WebSocket 连接等。

常规使用

常规参数一览

https://docs.gunicorn.org/en/stable/run.html#commonly-used-arguments

  1. 绑定地址和端口相关配置
    • ip:port
    • backlog: 定义等待连接队列的最大长度,在 TCP/IP 网络编程中,当一个服务器正在处理并发连接请求时,如果有更多的连接到达,当服务器当前无法及时处理时,这些请求将会被放置在一个等待连接队列中,等待服务器处理。
1
2
# 绑定端口 注意0.0.0.0的配置在docker网络中是关键配置
bind = f"0.0.0.0:{PORT}"
  1. 工作进程和并发性能配置
    • workers: 定义工作进程的数量,可以是整数值或者是 CPU 核心数的倍数
    • worker_class: 定义 worker 类型,例如:sync\gevent\eventlet 等
    • worker_connections: 每个工作进程允许的最大并发连接数
    • threads: 每个工作进程中的线程数(仅适用于支持线程的工作进程)
1
2
3
4
5
6
7
8
9
# 工作进程数 常规使用Docker横向扩容,故按项目实际情况配置
workers = 3
# 工作单位 除特殊情况外,工程无脑使用gevent 算法可考虑使用gthread
# 详见文档: https://docs.gunicorn.org/en/stable/design.html#choosing-a-worker-type
# 不使用sync的最大问题在于sync虽然在短连接中网络模式最为简单干净,但不支持长连接,在高并发任务中的TCP效率会非常低
worker_class = "gevent"
# 客户端最大同时连接数 在极高并发根据服务器和实例数根据情况修改
# 仅适用于eventlet/gevent
worker_connections = 5000
  1. 进程管理和重启配置
    • max_requests: 每个工作进程处理的最大请求数,达到后将自动重启
    • max_requests_jitter: 请求处理最大请求数的抖动范围
    • timeout: 工作进程处理请求的超时时间
    • graceful_timeout: 优雅关闭工作进程的超时时间
1
2
3
4
5
6
7
8
9
# 3. 进程管理和重启配置 -> max_requests && max_requests_jitter && timeout && graceful_timeout
# 每执行多少请求,即重启服务 该功能用于防止内存泄漏
# 对算法来说,该功能会导致重新初始化服务 可以设置0为关闭
max_requests = 50000
# 在max_requests 的基础上,随机加减 max_requests_jitter 的参数值 该功能用于防止并发下所有容器同时重启
# 当max_requests=0 时,该配置不生效
max_requests_jitter = 1000
# 超时时间 根据实际情况配置 推荐30/60
timeout = 600
  1. 日志和调试配置
    • accesslog: 访问日志文件的路径,或者使用特定的值(例如 -)输出到标准输出
    • errorlog: 错误日志文件的路径,或者使用特定的值(例如 -)输入到标准输出
    • loglevel: 日志记录级别,例如 degub,info, warning
1
2
3
4
# 是否使用debug模式
debug = DEBUG
# 是否重定向错误到日志文件
capture_output = True
1
CMD ["gunicorn", "-c", "gunicorn.conf.py", "server:create_app()", "–access-logfile -"]

how to choose worker

参考:https://docs.gunicorn.org/en/stable/design.html#choosing-a-worker-type

如何选择一个适用的 worker, Gunicorn 提供了多种类型的工作进程(Workers),每种

工作进程都有不同的工作模型和适用场景。以下是 Gunicorn 支持的常见工作类型:

  • Sync - 同步工作进程

    sync 默认的工作进程类型。它使用同步的方式处理请求,即每个工作进程一次只处理一个请求。适用于 IO 密集型的应用程序,但是在面对长时间运行的请求或高并发情况下可能会出现性能问题。

  • Async workers - 异步工作进程

    • gevent: 使用 gevent 库作为异步处理引擎。适用于 IO 密集型的应用程序,能够处理大量的并发连接,提高性能。
    • eventlet: 使用 eventlet 库作为异步处理引擎。类似于 gevent,适用于 IO 密集型的应用程序,具有良好的并发性能。
    • ……
  • Thread-based Workers (基于线程的工作进程)

    • sync: 使用多线程来处理请求。适用于处理计算密集型的任务,可以利用多核 CPU 的优势,但在面对 IO 密集型的应用程序时可能会有性能问题

gevent 介绍

gevent 是一个 Python 网络函数库,通过 Greenlet 协程 + libev 快速事件循环,实现了异步模型。

gevent 的主要特定和工作原理如下:

  • 协程 (Greenlet):gevent 使用 greenlet 库实现协程。协程是一种用户级线程,可以在同一个线程中切换执行。每个协程都有自己的执行上下文和栈,可以独立地运行和暂停,以及在适当的时机切换到其他协程。
  • 事件循环(Event Loop):gevent 使用事件循环机制来调度协程的执行。事件循环负责管理协程的调度和协程之间的切换。当一个协程被阻塞时(例如等待 I/O 操作完成),事件循环会切换到其他可运行的协程,以避免阻塞整个程序。
  • 非阻塞式 I/O:gevent 利用非阻塞式 I/O 来实现协程之间的切换。当一个协程遇到 I/O 操作时(例如网络请求或文件读写),它会将 I/O 操作交给 gevent 的事件循环处理,并切换到其他协程继续执行。当 I/O 操作完成时,事件循环会通知相应的协程,让器继续执行。
  • 基于 Monkey patching 的协程化:gevent 通过 Monkey Patching 技术,对 Python 标准库中的一些阻塞式 I/O 操作进行替换,使其变成非阻塞的。

gevent 如何实现并发

在 gevent 中,每个请求的连接都是一个 Greenlet 协程。Gevent 虽然只有一个线程、同时只能处理一个请求,但是在这个请求的异步事件没准备好时,进入到 IO 等待时,能主动 yield 让出控制权、而不是阻塞其他请求的协程,而是先让其他协程执行,当自己的 IO 准备好后,事件循环会将它从 yield 让出控制权的地方,继续恢复执行。

这样,Gevent 就能在不同的请求间不断切换,从而实现并发,以充分利用 CPU、减少 IO 等待时间。并且,切换的 Greenlet 是微线程,它操作的维度是函数,而不是线程/进程,所以来回切换的开销,就没有那么大。

同步 worker 和 异步 worker ,这两种 worker 类型是最常用的。一般而言,对于我们的应用而言,我们的 web app 多半属于 外部 IO 密集型(总要访问第三方服务等),所以用 Gunicorn 的 Gevent 异步 worker,就非常合理。

而如果你的 web app 是 CPU 密集型,或者你希望请求之间不要互相影响,那么可以选择 Gunicorn 的 同步 worker。

常见问题

常见问题:https://docs.gunicorn.org/en/stable/faq.html#faq

参考

https://stackoverflow.com/questions/36834234/eventlet-vs-greenlet-vs-gevent

https://docs.gunicorn.org/en/latest/design.html#how-many-workers

https://bbs.huaweicloud.com/blogs/309794

https://docs.gunicorn.org/en/stable/design.html#choosing-a-worker-type

-------------THANKS FOR READING-------------