多进程与多线程服务器最受益于哪些方面?

28

有人能解释一下每种并发方法的瓶颈在哪里吗?

像Unicorn(基于进程)和Puma(基于线程)这样的服务器。

每种方法喜欢CPU核心吗?线程?还是仅仅是时钟速度?或者是特定的组合?

如何确定在使用专用服务器的情况下需要的最佳CPU特性?

而在使用Unicorn的情况下,如何确定最佳的worker数量,或者在使用Puma的情况下,如何确定最佳的线程数量?

2个回答

60

Unicorn是基于进程的,这意味着每个Ruby实例都需要存在在自己的进程中。每个进程可能需要500MB左右的空间,这将迅速耗尽系统资源。而Puma是基于线程的,理论上不会使用同样数量的内存来达到相同的并发量。

因为Unicorn运行多个进程,所以不同进程之间会有并行性。这受限于你的CPU核心数(更多核心可以同时运行更多进程),但内核会在活动进程之间切换,因此可以运行超过4或8个进程(无论你有多少个核心)。 你的机器内存将限制你的进程数。 直到最近,Ruby不支持写时复制,这意味着每个进程都有自己继承的内存(Unicorn是一个preforking服务器)。Ruby 2.0支持了写时复制,这意味着Unicorn实际上不必将所有子进程加载到内存中。我不是100%清楚这一点。 阅读关于写时复制的文章,并查看Jessie Storimer的精彩书籍“使用Unix进程工作”。 我相信他在其中介绍了它。

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

针对 Unicorn,我认为瓶颈在于内存和时钟速度。针对 Puma,我会说瓶颈在于选择的解释器(MRI vs Rubinius 或 JRuby)和服务器正在执行的工作类型(大量CPU绑定任务 vs 非阻塞IO)。关于这个争论,有很多优秀的资源可供参考。请查看 Jessie Storimer 的书籍:《使用Ruby线程》《使用Unix进程》;阅读 ryan tomayko 的快速预分叉服务器摘要,并在谷歌上查找更多信息。 我不知道在您的情况下 Unicorn 或 Puma 的最佳工人数量是多少。最好的方法是运行性能测试并根据实际情况进行调整。没有一种通用的大小。 (尽管我认为 Puma 的标准是使用一个 16 线程池并将其锁定)

1
谢谢 Stuart 的清晰解释!! - CodeOverload
Ruby 2.0被称为“拷贝时复制友好”,这意味着GC不会破坏堆中共享的内存页面。在1.9版本中,您需要分叉大量工作进程,然而GC会运行并移动对象,完全摧毁在GC开始之前进程之间可能存在的COW共享优势。 - lamont
对于任何新来的访客,这个特定的回答有点过时了。Puma现在已经使用了进程和线程两种方式。你可能想看看Lamont的回答,了解更多关于Puma的信息。 - undefined

6

Puma实际上是多线程和多进程的。您可以在“集群模式”下调用它,它将生成多个分叉工作程序,这些程序将在MRI上的不同核心上运行。由于Puma是多线程的,因此可能适合运行与服务器上核心数量相等的进程数。因此,对于4核服务器,应该使用类似以下内容:

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

这将处理最多32个并发线程,其中最多4个线程同时在CPU上运行,并应该能够最大化服务器的CPU资源。 -preload 参数预加载应用程序,并利用Ruby 2.0 COW改进垃圾回收以减少RAM使用。
如果您的应用程序在等待其他服务(搜索服务、数据库等)方面花费相当长的时间,那么这将是一个很大的改进。当一个线程阻塞时,同一进程中的另一个线程可以抓住CPU并进行工作。在这个例子中,您可以支持最多32个并行请求,而只占用4个进程的RAM。
使用Unicorn,您必须fork出32个worker,这将占用32个进程的RAM,非常浪费。
如果您的应用程序只是CPU计算,则这将是高度低效的,您应该减少独角兽的数量,Puma比Unicorn的优势将减小。但是,在独角兽的情况下,您必须对您的应用程序进行基准测试并找出正确的数字。 Puma通常会通过生成更多的线程来进行优化,其性能应该不会比独角兽差(在纯CPU情况下),并且在睡眠时间很长的应用程序的情况下,其性能应该比独角兽好得多。
当然,如果您使用Rubinius或JRuby,则没有竞争,并且可以生成一个运行多核并处理所有32个线程的进程。
简而言之,我认为独角兽比Puma没有太多优势,因为Puma实际上使用了两种模型。
当然,我不知道Puma与Unicorn在运行生产软件方面的可靠性如何。要关注的一件事是,如果您在一个线程中涂写任何全局状态,它可能会影响同时执行的其他请求,从而可能产生不确定的结果。由于独角兽不使用线程,因此不存在并发问题。我希望到这个时候,Puma和Rails都已经成熟,具有关于并发问题的能力,并且Puma可以在生产中使用。但是,我不一定期望我在GitHub上找到的每个rails插件和rubygem都是线程安全的,并且预计需要做一些额外的工作。但是,一旦您足够成功以至于在第三方库中找到线程问题,那么您可能已经足够大,无法承担运行如此多的独角兽进程的RAM成本。另一方面,我了解并发错误,并且我擅长Ruby,因此调试成本可能比购买云中的RAM成本低得多。 YMMV。
还要注意,我不确定您是否应该计算超线程核心还是物理核心来估计传递给“-w”的值,并且您需要进行性能测试,以及对于-t使用什么值进行性能测试。尽管即使您运行的进程数量是您'需要'的两倍,内核中的进程调度程序也应该能够处理它,直到您饱和CPU为止,然后您将遇到更大的问题。在MRI上,我可能会建议为每个超线程核心启动一个进程。

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