什么是预派生(pre-fork)的Web服务器模型?

161
我想知道当Web服务器自称为预分叉Web服务器时,它到底意味着什么。 我有一些例子,例如用于Ruby的unicorn和用于Python的gunicorn

更具体地说,以下是这些问题:

  • 这个模型解决了什么问题?
  • 预分叉Web服务器最初启动时会发生什么?
  • 它如何处理请求?

此外,对于unicorn/gunicorn还有一个更具体的问题:

假设我有一个Web应用程序,我想在(g)unicorn上运行。 在初始化时,Web应用程序将执行一些初始化操作(例如,填充其他数据库条目)。 如果我使用多个进程来配置(g)unicorn,那么初始化操作会运行多次吗?

2个回答

150

预派生基本上意味着主进程创建负责每个请求的子进程。 一个fork是完全独立的*nix进程。

根据下面的评论更新。 prepre-fork 中的意思是在请求到来之前派生这些进程。 然而,随着负载的增加和减少,它们通常可以增加或减少。

当您拥有不支持线程安全的库时,可以使用预先派生。 它还意味着请求中的问题只会影响处理它们的进程,而不是整个服务器。

初始化运行多次取决于您部署的内容。 通常,连接池和类似的东西对于每个进程都会存在。

在线程模型中,主进程将创建轻量级线程以分派请求。 但如果线程引起严重问题,则可能会对主进程产生后果。

使用诸如Nginx、Apache 2.4的事件MPM或gevent(可与Gunicorn一起使用)等工具是异步的,这意味着一个进程可以处理数百个请求而不会阻塞。


54
我对“prefork”的含义也曾有过疑问。当然,我认为它意味着某种分叉,但是“pre”这部分让我感到困惑。我在这里找到了答案:http://www.abbreviations.com/prefork。实际上,“pre”部分表示预先创建工作进程,这样就不会浪费时间在只有需要工作进程时才进行分叉。这对我来说很有道理 : ) - El Ninja Trepador
1
// @ElNinjaTrepador,为什么不添加一个单独的答案?对我来说,那更容易理解,如果该评论得到更突出的位置,可能会更有帮助。 - Nathan Basanese
2
我已更新答案,以在“pre-fork”中添加更多信息。@ElNinjaTrepador感谢您指出这一点,我没有意识到这不是众所周知的。 - Joe Doherty
1
@NathanBasanese会做,先生 ;) - El Ninja Trepador
4
@JoeDoherty也许这已经是众所周知的,只是在那时候我不知道而已:D我原本打算加上另一个答案(根据Nathan的要求),但既然你把我说的加到你的答案里了,那就不必要了:) - El Ninja Trepador

28

什么是“预派生工作模型”?

  • 主进程:有一个主进程来根据负载和硬件容量创建和销毁工作进程。更多的请求会导致主进程创建更多的工作进程,直到达到“硬件极限”(例如所有CPU都被占满),此时将启用队列。
  • 工作进程:一个工作进程可以被理解为应用程序/服务器的实例。因此,如果有4个工作进程,则服务器将被启动4次。这意味着它占用了比一个工作进程多4倍的“基本RAM”,除非您进行了共享内存操作。
  • 初始化:您的初始化逻辑需要足够稳定,以考虑到多个服务器。例如,如果您编写数据库条目,请检查它们是否已经存在或在您的应用服务器之前添加设置作业。
  • 预派生:prefork中的“pre”表示主进程始终添加比当前所需的更多容量,以便系统“已准备就绪”如果负载增加。因此,它会预先生成一些工作进程。例如,在 Apache库中,您可以使用MinSpareServers属性来控制此功能。
  • Requests: 请求(TCP连接句柄)从主进程传递给子进程。
  • 预派生服务器解决了什么问题?

    • 多进程 如果您有一个只能针对一个CPU核心的程序,那么只生成一个服务器可能会浪费一些硬件容量。预派生的工作进程解决了这个问题。
    • 稳定性:当一个工作进程崩溃时,主进程不受影响。它可以重新生成一个工作进程。
    • 线程安全由于实际上您的服务器是在不同的进程中启动的,因此您不需要担心线程安全性(因为没有线程)。这意味着它是一个适合使用非线程安全代码或非线程安全库的模型。
    • 速度:由于子进程不是在需要时才派生(生成),而是预先派生(生成),因此服务器总是能够快速响应。

    替代方案和其他注意事项

    • 容器编排: 如果您熟悉容器化和容器编排工具,例如kubernetes,则会发现许多问题也可以通过它们得到解决。Kubernetes为多进程生成多个pod,它具有相同(或更好)的稳定性,并且还有诸如“水平pod自动缩放器”之类的东西,也可以生成和销毁工作者。
    • 线程: 服务器可以为每个传入请求生成一个线程,这使得许多请求可以“同时”处理。这是大多数基于Java的Web服务器的默认设置,因为Java本身对线程有良好的支持。良好的支持意味着线程在不同的CPU核心上真正并行运行。另一方面,Python的线程由于GIL(全局解释器锁)无法真正并行化(将工作分散到多个核心),它们只提供一种进行上下文切换的方式。 在此了解更多信息。这就是为什么对于Python服务器,“预派生者”(例如gunicorn)如此受欢迎,而来自Java的人可能以前从未听说过这种东西。
    • 异步/非阻塞处理: 如果你的服务器花费很多时间在“等待”,例如磁盘I / O、对外部服务的http请求或数据库请求上,则多进程可能不是你想要的。相反,考虑使代码“非阻塞”,意味着它可以同时处理许多请求。像Python中的fastapi(ASGI服务器)、Go或nodejs这样基于Async / await(协程)的系统使用此机制,这样即使一个服务器也可以同时处理多个请求。
    • CPU绑定任务: 如果你有CPU绑定任务,则上面提到的非阻塞处理将无济于事。然后,您需要某种方式进行多进程处理以分配负载到您的CPU核心上,解决方案是:容器编排、线程(在允许真正并行化的系统上)或...预先派生的工作进程。

    来源


    非常有用的答案bersling,很高兴我读完了这个问题! :) - scanny

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接