谁能解释每种并发方法的瓶颈是什么?
像Unicorn(基于进程)和Puma(基于线程)这样的服务器。
每种方法是否更喜欢 CPU 内核?线程?或者只是时钟速度?还是特殊的组合?
在使用专用服务器的情况下,如何确定所需的最佳 CPU 特性?
以及如何确定 Unicorn 的最佳工人数量,或 Puma 的线程数量?
谁能解释每种并发方法的瓶颈是什么?
像Unicorn(基于进程)和Puma(基于线程)这样的服务器。
每种方法是否更喜欢 CPU 内核?线程?或者只是时钟速度?还是特殊的组合?
在使用专用服务器的情况下,如何确定所需的最佳 CPU 特性?
以及如何确定 Unicorn 的最佳工人数量,或 Puma 的线程数量?
Unicorn 是基于进程的,这意味着每个 ruby 实例都必须存在于自己的进程中。对于每个进程,这可能在 500mb 左右,这将很快耗尽系统资源。Puma 是基于线程的,理论上不会使用相同数量的内存来获得相同数量的并发性。
Unicorn是运行多个进程的,不同进程之间将具有并行性。这受到您的 CPU 内核的限制(更多内核可以同时运行更多进程),但内核将在活动进程之间切换,因此可以运行超过 4 或 8 个进程(无论您拥有多少内核)。您将受到机器内存的限制。直到最近,ruby 对写时复制并不友好,这意味着每个进程都有自己的继承内存(独角兽是一个预分叉服务器)。Ruby 2.0 对写时复制友好,这可能意味着 unicorn 实际上不必将所有子进程加载到内存中。我不是 100% 清楚这一点。阅读有关写入时复制的内容,并查看 jessie storimer 的精彩书籍“使用 unix 进程工作”。我很确定他把它盖在那里。
Puma 是一个线程服务器。MRI Ruby,由于全局解释器锁 (GIL),只能运行单个 CPU 绑定任务一次(参见 ruby 小吃第 127 集,平行谎言)。它将在线程之间进行上下文切换,但只要它是一个 CPU 绑定任务(例如数据处理),它就只会运行一个执行线程。如果您使用不同的 Ruby 实现(例如 JRuby 或 Rubinius)运行服务器,这会变得很有趣。他们没有 GIL,可以并行处理大量信息。JRuby 非常快,而 Rubinius 与 MRI 相比速度较慢,但多线程 Rubinius 处理数据的速度比 MRI 快。然而,在非阻塞 IO 期间(例如写入数据库、发出 Web 请求),MRI 将上下文切换到非执行线程并在那里工作,然后在返回信息时切换回前一个线程。
对于Unicorn,我想说瓶颈是内存和时钟速度。对于Puma,我想说瓶颈是您选择的解释器(MRI vs Rubinius 或 JRuby)以及您的服务器正在执行的工作类型(大量 cpu 绑定任务与非阻塞 IO)。
这场辩论有大量的资源。查看 Jessie Storimer 关于这些主题的书籍,使用 ruby 线程和使用 unix 进程;阅读ryan tomayko 对预分叉服务器的快速摘要,并在谷歌周围搜索更多信息。
在您的情况下,我不知道 Unicorn 或 Puma 的最佳工人数量是多少。最好的办法是运行性能测试并做适合您的事情。没有一种尺寸适合所有人。(虽然我认为 puma 标准是使用 16 个线程池并将其锁定)
Puma 实际上是多线程和多进程的。您可以在“集群模式”中调用它,它会产生多个分叉的工作人员,这些工作人员将在 MRI 的不同核心上运行。由于 Puma 是多线程的,它可能适合运行与服务器上的内核数相等的进程数。所以对于一个 4 核服务器来说,这样的东西是合适的:
puma -t 8:32 -w 4 --preload
这将处理最多 32 个并发线程,最多同时在 CPU 上运行 4 个线程,并且应该能够最大化服务器上的 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 插件和 rubygem 都是线程安全的,并且希望必须做一些额外的工作。但是一旦你成功地在第三方库中发现线程问题,你可能已经足够大了,你可以' 无法负担运行这么多 Unicorn 进程的 RAM 成本。OTOH,我了解并发错误并且我对 Ruby 很熟悉,因此调试成本对我来说可能比在云中购买 RAM 的成本要低得多。YMMV。
另请注意,我不确定在估计传递给“-w”的值时是否应该计算超线程内核或物理内核,并且您需要自己进行性能测试,以及性能测试要用于 -t 的值。尽管即使你运行两倍于你“需要”的进程数量,内核中的进程调度程序也应该能够毫无问题地处理这个问题,直到你的 CPU 饱和,在这种情况下,无论如何你都会遇到更大的问题。我可能会建议为每个超线程内核启动一个进程(在 MRI 上)。