【问题标题】:Python multiprocessing within Flask request with Gunicorn + Nginx使用 Gunicorn + Nginx 在 Flask 请求中进行 Python 多处理
【发布时间】:2021-02-07 08:50:38
【问题描述】:

我想构建一个能够处理的服务:

  • 请求量少
  • 每个请求的计算成本很高
  • 但是可以并行处理高计算成​​本。

我对 pre-fork 服务器的理解是会发生如下情况:

  1. 服务器启动
  2. Gunicorn 创建多个操作系统进程,也称为工作器,准备好接受请求
  3. 请求进来。Nginx 转发到 Gunicorn。 Gunicorn 发送给其中一名工人。

我想了解的是,如果在我的 Flask 代码中,在处理请求时,我有这个:

from multiprocessing import pool as ProcessPool
with ProcessPool(4) as pool:
    pool.map(some_expensive_function, some_data)

特别是:

  1. 是否会启动其他操作系统进程?加速会是我所期望的吗? (即,类似于我在 Flask 生产环境之外运行 ProcessPool 吗?)如果 Gunicorn 创建了 4 个网络工作者,那么现在会有 7 个操作系统进程在运行吗? 9?有没有做太多的风险? Gunicorn 是否假设每个工人都不会分叉或不在乎?
  2. 如果 web-worker 在启动 ProcessPool 后死亡或被杀死,上下文管理器是否会正确关闭它?
  3. 这是明智之举吗?有哪些替代方案?

【问题讨论】:

  • 您成功解决了这个挑战吗?如果是这样,请问如何?我目前正在处理类似的困境。
  • @CaspervanLit 我通过将处理移出烧瓶并转移到同一主机上的 RPC 服务器上解决了这个问题。我还是不知道上面的答案。

标签: python nginx flask process gunicorn


【解决方案1】:

好问题!使用 Python 多处理,可以使用 3 种“启动方法”,它们都对您的问题有影响。 As the docs explain,他们是:

  • 'spawn':父进程启动一个新的python解释器进程。子进程只会继承运行进程对象的run() 方法所需的资源。特别是,不会继承父进程中不必要的文件描述符和句柄。与使用 fork 或 forkserver 相比,使用这种方法启动进程相当慢。在 Unix 和 Windows 上可用。 Windows 和 macOS 上的默认设置。
  • 'fork':父进程使用os.fork() fork Python 解释器。子进程在开始时实际上与父进程相同。父进程的所有资源都由子进程继承。请注意,安全地分叉多线程进程是有问题的。仅在 Unix 上可用。 Unix 上的默认设置。
  • 'forkserver'当程序启动并选择forkserver启动方式时,会启动一个服务器进程。从那时起,每当需要一个新进程时,父进程都会连接到服务器并请求它派生一个新进程。 fork 服务器进程是单线程的,因此使用os.fork() 是安全的。没有不必要的资源被继承。在支持通过 Unix 管道传递文件描述符的 Unix 平台上可用。

至于 Gunicorn 的 pre-fork 模型,你已经解释得很好了。每个工人都在自己的进程中运行。由于您尝试在工作人员中使用多处理,而不是与 Gunicorn 一起使用,这应该是可行的,但仍然会有点错误。

import multiprocessing

mp = multiprocessing.get_context('spawn')

这段代码为我们提供了mp 对象,它具有与多处理模块相同的API,但具有设置的启动方法。在上面的代码中,它被设置为'spawn'。这是在 Gunicorn worker 中使用多处理的最安全途径,因为它与创建它的进程最隔离,并且不太可能遇到意外共享资源的问题。

with mp.Pool(processes=4) as pool:
    pool.map(some_expensive_function, some_data)

然后我们使用mp 对象来创建一个进程池,就像您所做的那样。此代码必须位于仅在工作进程中调用/使用的函数/模块内。如果在服务器进程中使用它可能会导致问题。

  1. 是否会启动其他操作系统进程?加速会是我所期望的吗? (即,类似于我在 Flask 生产环境之外运行 ProcessPool 吗?)

这里有很多问题。将启动其他操作系统进程。加速可能会有很大差异,并且取决于许多因素,例如:

  • 还有多少其他进程正在运行? Gunicorn 运行了多少个工作处理器?
  • 服务器负载过重吗?
  • 处理器有多少个内核?
  • 工作的并行性如何? some_expensive_function(data_1) 是否必须等待 some_expensive_function(data_2) 才能工作?

要弄清楚使用多处理是否更快,以及它会快多少,您必须对其进行测试。在此之前,您可以做的最好的事情是根据上面列出的因素进行粗略估计。

  1. (续)如果 Gunicorn 创建了 4 个网络工作者,那么现在会有 7 个操作系统进程在运行吗? 9?有没有做太多的风险? Gunicorn 是否假设每个工人都不会分叉或不在乎?

如果有 4 个 Gunicorn 工作进程,并且每个都在用 4 个进程完成一个使用多处理的请求,那么将有 1 个 Gunicorn 父进程 + 4 个工作进程 + 4 * 4 个工作子进程 = 21 个进程,而不是提及 Nginx 使用的进程。

Gunicorn 建议您创建 (2 * num_cores) + 1 worker,但在您的情况下,您可能希望通过将其除以 4 来减少它,以说明您的 worker 进程本身在使用多个内核时工作得最好。要找到最有效的配置,您必须对各种配置进行基准测试,以找出最适合您的配置。

  1. 如果 web-worker 在启动 ProcessPool 后死亡或被杀死,上下文管理器是否会正确关闭它?

这取决于工人的死亡方式。如果它通过 SIGKILL 被杀死,或者遇到分段错误或其他一些严重错误,那么它将突然死亡,而无需运行任何终结代码。上下文管理器只能在 try-finally 块能够执行“finally”块的情况下完成其工作。有关更多信息,请查看此答案:Does 'finally' always execute in Python?

  1. 这是明智之举吗?有哪些替代方案?

这本身并不疯狂,但这不是我通常推荐的那种方法。一种替代方法是使用自己的服务器实现some_expensive_function。您的 Gunicorn 工作人员可以使用 IPC 或网络通信将工作发送到 some_expensive_function 服务器进程,它会处理在子进程之间分配这项工作。此类设计的一个优点是,如果性能需要,some_expensive_function 服务器进程可以轻松移动到另一台计算机上运行。

这类似于数据库通常作为自己的服务器进程运行的方式,并且可以位于同一台计算机或单独的计算机上(可能位于用于只读查询的负载平衡器或分片配置之后),具体取决于必须满足哪些性能要求。

如果您决定走这条路,您可能会发现 Python 包 Celery 可用于分发 Gunicorn 工作人员的工作。


如果您想这样做,您可能应该使用preload_app=True 运行 Gunicorn。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-18
    • 1970-01-01
    • 1970-01-01
    • 2020-04-14
    • 1970-01-01
    • 2014-01-13
    • 2010-09-18
    相关资源
    最近更新 更多