TL;DR,对于受 CPU 限制的应用程序,增加工作人员和/或内核。对于 I/O 有界应用程序,请使用“伪线程”。
Gunicorn是一个 Python WSGI HTTP 服务器,它通常位于反向代理(例如,Nginx)或负载均衡器(例如,AWS ELB)和 Web 应用程序(例如 Django 或 Flask)之间。
Gunicorn 实现了一个 UNIX 预分叉网络服务器。
太好了,什么意思?
为了在使用 Gunicorn 时提高性能,我们必须牢记 3 种并发方式。
每个 worker 都是一个加载 Python 应用程序的 UNIX 进程。工人之间没有共享内存。
建议的数量workers
是(2*CPU)+1
。
对于双核(2 个 CPU)机器,建议workers
值为5 。
gunicorn --workers=5 main:app
具有默认工作类(同步)的 Gunicorn。请注意图像中的第 4 行:“Using worker:sync”。
Gunicorn 还允许每个 worker 拥有多个线程。在这种情况下,Python 应用程序为每个 worker 加载一次,并且由同一个 worker 产生的每个线程共享相同的内存空间。
要在 Gunicorn 中使用线程,我们使用threads
设置。每次我们使用时threads
,worker 类都设置为gthread
:
gunicorn --workers=5 --threads=2 main:app
带有线程设置的 Gunicorn,它使用 gthread 工作类。请注意图像中的第 4 行:“Using worker:threads”。
上一条命令与以下命令相同:
gunicorn --workers=5 --threads=2 --worker-class=gthread main:app
workers * threads
在我们的例子中,最大并发请求数为10。
使用工作线程和线程时建议的最大并发请求数仍为(2*CPU)+1
.
因此,如果我们使用四核(4 个 CPU)机器,并且想要混合使用 worker 和线程,我们可以使用 3 个 worker 和 3 个线程,以获得 9 个最大并发请求。
gunicorn --workers=3 --threads=3 main:app
有一些 Python 库,例如gevent和Asyncio,它们通过使用协程实现的“伪线程”在 Python 中启用并发。
Gunicorn 允许通过设置它们相应的工作类来使用这些异步 Python 库。
这里的设置适用于我们要使用的单核机器gevent
:
gunicorn --worker-class=gevent --worker-connections=1000 --workers=3 main:app
worker-connections 是 gevent worker 类的特定设置。
(2*CPU)+1
仍然是建议的,workers
因为我们只有 1 个核心,我们将使用 3 个工人。
在这种情况下,最大并发请求数为 3000(3 个工作人员 * 每个工作人员 1000 个连接)
在 Python 中,线程和伪线程是并发的手段,但不是并行的;而工人是并发和并行的手段。
这些都是很好的理论,但是我应该在我的程序中使用什么?
通过调整 Gunicorn 设置,我们希望优化应用程序性能。
workers
to的值来支持这种编程范式(2*CPU)+1
。workers
建议值(2*CPU)+1
,理解并行请求的最大数量是内核数。threads
和其对应的g线程工人阶级有利于workers
产生更好的性能,因为应用程序加载每个工人和工人股运行一些内存每个线程一次,这涉及到一些费用额外的 CPU 消耗。workers
为(2*CPU)+1
,不要担心threads
。从那时起,所有的基准测试都是反复试验。如果瓶颈是内存,就开始引入线程。如果瓶颈是 I/O,请考虑不同的 Python 编程范式。如果瓶颈是CPU,考虑使用更多的内核并调整workers
值。我们软件开发人员通常认为每个性能瓶颈都可以通过优化应用程序代码来解决,但事实并非总是如此。
有时,调整 HTTP 服务器的设置、使用更多资源或重新构建应用程序以使用不同的编程范式是我们提高整体应用程序性能所需的解决方案。
在这种情况下,构建系统意味着了解我们可用于部署高性能应用程序的计算资源类型(进程、线程和“伪线程”)。
通过使用正确的资源理解、构建和实施正确的技术解决方案,我们可以避免陷入试图通过优化应用程序代码来提高性能的陷阱。