【问题标题】:What do multi-processes VS multi-threaded servers most benefit from?多进程 VS 多线程服务器最受益的是什么?
【发布时间】:2013-09-05 16:21:46
【问题描述】:

谁能解释一下每种并发方法的瓶颈是什么?

Unicorn(基于进程)和 Puma(基于线程)等服务器。

每种方法都偏爱 CPU 内核吗?线程?或者只是时钟速度?还是特殊的组合?

如何确定使用专用服务器时所需的最佳 CPU 特性?

在 Unicorn 的情况下如何确定最佳工人数量,在 Puma 的情况下如何确定线程数量?

【问题讨论】:

    标签: ruby-on-rails ruby multithreading unicorn puma


    【解决方案1】:

    Unicorn 是基于进程的,这意味着每个 ruby​​ 实例都必须存在于自己的进程中。对于每个进程,这可能在 500mb 左右,这将很快耗尽系统资源。 Puma 基于线程,理论上不会使用相同数量的内存来获得相同数量的并发。

    独角兽,即运行多个进程,将在不同进程之间具有并行性。这受到您的 CPU 内核的限制(更多内核可以同时运行更多进程),但内核将在活动进程之间切换,因此可以运行超过 4 或 8 个进程(无论您拥有多少内核)。您将受到机器内存的限制。直到最近,ruby 对写时复制并不友好,这意味着每个进程都有自己的继承内存(独角兽是一个预分叉服务器)。 Ruby 2.0 对写时复制友好,这可能意味着 unicorn 实际上不必将所有子进程加载到内存中。我不是100%清楚这一点。阅读有关写时复制的内容,并查看 jessie storimer 的精彩书籍“使用 unix 进程”。我很确定他把它盖在了那里。

    Puma 是一个线程服务器。 MRI Ruby,由于全局解释器锁 (GIL),一次只能运行一个 CPU 绑定任务(参见 ruby​​ tapas 第 127 集,并行 fib)。它将在线程之间进行上下文切换,但只要它是一个 CPU 绑定任务(例如数据处理),它就只会运行一个执行线程。如果您使用不同的 Ruby 实现(如 JRuby 或 Rubinius)运行服务器,这会变得很有趣。他们没有 GIL,可以并行处理大量信息。 JRuby 相当快,而 Rubinius 与 MRI 相比速度较慢,但​​多线程 Rubinius 处理数据的速度比 MRI 快。然而,在非阻塞 IO 期间(例如写入数据库、发出 Web 请求),MRI 将上下文切换到非执行线程并在那里工作,然后在返回信息时切换回前一个线程。

    对于Unicorn,我想说瓶颈是内存和时钟速度。对于 Puma,我想说瓶颈是您选择的解释器(MRI 与 Rubinius 或 JRuby)以及您的服务器正在执行的工作类型(大量 cpu 绑定任务与非阻塞 IO)。

    关于这场辩论有大量的资源。查看 Jessie Storimer 关于这些主题的书籍,working with ruby threadsworking with unix processesread this quick summary of preforking servers ryan tomayko,谷歌搜索更多信息。

    在你的情况下,我不知道 Unicorn 或 Puma 的最佳工人数量是多少。最好的办法是运行性能测试并做适合您的事情。没有一种尺寸适合所有人。 (虽然我认为 puma 标准是使用 16 个线程的池并将其锁定)

    【讨论】:

    • 感谢 Stuart 的澄清!!
    • ruby 2.0 是“写时复制友好”意味着 GC 不会与堆中的写页面上的副本共享垃圾。在 1.9 中,您将分叉出一堆独角兽,然后 GC 将运行并移动对象,并完全破坏在 GC 开始之前您在这些进程之间可能拥有的任何 COW 共享优势。
    【解决方案2】:

    Puma 实际上是多线程和多进程的。您可以在“集群模式”中调用它,它会产生多个分叉的工作人员,这些工作人员将在 MRI 的不同核心上运行。由于 Puma 是多线程的,它可能适合运行与服务器上的内核数相等的进程数。所以对于一个 4 核服务器来说,这样的东西是合适的:

    puma -t 8:32 -w 4 --preload
    

    这将处理多达 32 个并发线程,其中多达 4 个线程同时在 CPU 上运行,并且应该能够最大限度地利用服务器上的 CPU 资源。 --preload 参数预加载应用程序并利用 ruby​​ 2.0 COW 对垃圾收集的改进来减少 RAM 使用。

    如果您的应用花费大量时间等待其他服务(搜索服务、数据库等),那么这将是一个很大的改进。当一个线程阻塞时,同一进程中的另一个线程可以抢占 CPU 并开始工作。在此示例中,您最多可以并行支持 32 个请求,而仅在 RAM 中运行 4 个进程。

    如果使用 Unicorn,您将不得不分叉 32 个工作人员,这将承受在 RAM 中运行 32 个进程的影响,这是非常浪费的。

    如果您的应用程序所做的只是 CPU 运算,那么这将非常低效,您应该减少独角兽的数量,而 Puma 相对于独角兽的优势将会降低。但在 Unicorn 案例中,您必须对您的应用程序进行基准测试并找出正确的数字。 Puma 将倾向于通过产生更多线程来优化自身,其性能范围应该从不比 Unicorn 差(在纯 CPU 情况下)到比 Unicorn 好得多(在应用程序休眠很多的情况下)。

    当然,如果您使用 Rubinius 或 JRuby,那么就没有什么可竞争的了,您可以生成一个运行多核并处理所有 32 个线程的进程。

    TL;DR 是我不认为 Unicorn 比 Puma 有太多优势,因为 Puma 实际上使用了这两种模型。

    当然,我对 Puma vs Unicorn 在现实世界中运行生产软件的可靠性一无所知。需要关注的一件事是,如果您在一个线程中随意涂抹任何全局状态,它可能会影响同时执行的其他请求,这可能会产生不确定的结果。由于 Unicorn 不使用线程,因此不存在并发问题。我希望此时 Puma 和 Rails 在并发问题方面都已经成熟,并且 Puma 可以在生产中使用。但是,我不一定希望我在 GitHub 上找到的每个 rails 插件和 ruby​​gem 都是线程安全的,并且希望必须做一些额外的工作。但是,一旦您成功地在第三方库中发现线程问题,您可能已经足够大,以至于您无法负担运行这么多 Unicorn 进程的 RAM 成本。 OTOH,我了解并发错误并且我对 Ruby 很熟悉,因此调试成本对我来说可能比在云中购买 RAM 的成本要低得多。 YMMV。

    另外请注意,我不确定您是否应该计算超线程内核或物理内核来估算传递给“-w”的值,并且您需要自己进行性能测试,以及性能测试要使用的值-t。尽管即使您运行的进程数量是您“需要”的两倍,但内核中的进程调度程序应该能够毫无问题地处理该问题,直到您的 CPU 饱和,在这种情况下,无论如何您都会遇到更大的问题。我可能会建议为每个超线程内核启动一个进程(在 MRI 上)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-24
      • 1970-01-01
      • 2018-01-19
      • 2013-05-21
      相关资源
      最近更新 更多