Ruby 3,并发与生态

2021-01-09 14:11:29

在Ruby 3.0版本中,关于并发性,并行性和异步IO的讨论很多。

就我自己的看法,我想写下这对应用程序的性能和容量/成本意味着什么,以及对Ruby生态系统的影响。

我将假定读者已经知道UNIX中的线程与进程模型之间的差异以及《利特尔定律》。

借鉴其他语言总是很好的。 Cal Paterson撰写了一篇出色的文章“异步Python不会更快”。

它认为,与异步IO驱动的服务器相比,基于进程的(又称为分叉)Web服务器显示出更好的Web请求延迟。

但为什么?那是因为异步IO带来了协作式调度,这意味着仅对诸如await之类的语言关键字产生执行。

引用作者的话,这意味着执行时间不会“公平地”分配,并且一个线程在工作时可能会无意中使另一个CPU饥饿。这就是延迟更不稳定的原因。

相反,传统的同步Web服务器使用内核调度程序的抢先式多处理功能,该功能通过定期从执行中换出进程来确保公平性。这意味着可以更公平地分配时间,并且延迟差异也较小。

尽管异步IO减少了上下文切换,但它增加了整体延迟-对于后台作业而言值得,但对于Web请求而言却值得。

尽量减少上下文切换是一件好事,因为调度程序从一个任务切换到另一个任务总是会增加一点开销。由于这种情况每秒发生数千次,因此较少的上下文切换意味着更少的CPU周期浪费。

根据工作负载,我们可以以较少的上下文切换来换取更糟的延迟,或者反之。

Ractor模式通过限制要并行执行的代码块的共享状态,从而允许多个Ruby线程并行执行(以前在Ruby中是不可能的)。这些“代码块”(也称为“演员”)也可以通过消息相互交谈。这是其他语言中使用的Actor模型。

我们可以通过两种方式在现代应用程序中使用Ractor:从顶部(将每个工作人员包装到Ractor中)和从底部(在现有代码中选择性使用Ractor来并行化CPU密集型工作)。

尽管我认为可以从顶层方法中获得更多收益,但似乎在Ruby库中有如此多的共享和可变状态,这将非常棘手,尽管并非不可能。社区可能需要一些努力和至少一年的工作才能将图书馆推向较少共享的状态。在接下来的一年,我们将主要看到Ractor逐渐成熟并被“底”用例所采用。

从上面的观点来看,我可以看到Web服务器在一段时间内(例如Unicorn)继续使用流程(又称分叉)模型。 那些考虑到线程安全性而构建的应用程序将继续使用Puma(也许在集群模式下)。 对于诸如后台作业之类的异步工作负载,我可以看到生态系统正在慢慢进入异步IO模型。 使Sidekiq通过事件循环(而不是线程)并发执行作业可以提高吞吐量并节省CPU工作,尤其是对于像Webhook交付这样的IO绑定工作负载而言。 我们需要推动Ruby生态系统具有较少的共享状态,以充分利用Ractor模式。 这将花费我们一些时间。