如何部署一个线程安全的异步Rails应用程序?

8
我已经阅读了大量涉及 Ruby 和 Rails 不同版本中线程安全和性能的内容,我认为现在我对这些问题有了比较好的理解。
但是,在讨论如何实际部署异步 Rails 应用程序时,似乎缺少一些奇怪的东西。在谈论应用程序中的线程和同步性时,人们想要优化两件事情:
1. 最小化 RAM 使用率,利用所有 CPU 核心。
2. 能够在之前的请求等待 IO 时服务新请求。
第一点是人们对 JRuby 的(正确)兴奋之处。对于这个问题,我只尝试优化第二点。
假设这是我应用程序中唯一的控制器:
class TheController < ActionController::Base
  def fast
    render :text => "hello"
  end

  def slow
    render :text => User.count.to_s
  end
end
fast没有IO,可以每秒服务数百或数千个请求,而slow必须通过网络发送请求,等待工作完成,然后通过网络接收答案,因此比fast慢得多。

因此,理想的部署将允许在等待IO的情况下满足对fast的数百个请求。

似乎在网络讨论中缺少的是哪个层面的堆栈负责启用此并发。thin有一个--threaded标志,它将“以线程方式调用Rack应用程序[实验性]”--这是否为每个传入请求启动一个新线程?在线程中拆分rack应用程序实例,这些实例会持续存在并等待传入请求吗?

thin是唯一的方法还是还有其他方法?对于优化点2,Ruby运行时是否重要?


我认为你的 Ruby 版本对第二点并没有太大影响。这更取决于服务器的并发实现方式以及 Rails 本身的构建方式。 - providence
1个回答

6
你采用的方法取决于你的slow方法正在做什么。在完美的世界中,你可以使用类似于sinatra-synchrony的宝石来处理每个请求中的一个纤维。你只会受到纤维数量的限制。不幸的是,纤维的堆栈大小是硬编码的,在Rails应用程序中很容易超出。此外,我读到了一些恐怖故事,讲述了调试纤维的困难,由于异步IO被启动后自动产生的屈服。使用纤维时仍然可能存在竞争条件。目前,在Web应用程序的前端,基于纤维的Ruby还有点像贫民窟。
一种更实际的解决方案是使用具有工作线程池的Rack服务器,如Rainbows!或Puma。我相信Thin的--threaded标志会在新线程中处理每个请求,但是启动本机操作系统线程并不便宜。最好使用线程池,并将池大小设置足够高。在Rails中,不要忘记在生产中设置config.threadsafe!
如果您愿意更改代码,您可以查看Konstantin Haase在实时Rack上的演讲。他讨论了如何使用EventMachine::Deferrable类在传统的请求/响应周期之外生成响应。这似乎非常棒,但您必须以异步风格重写代码。
还可以查看CrampGoliath。这些让您在与Rails应用程序一起托管的单独Rack应用程序中实现您的“slow”方法,但您可能还需要重写代码以适应Cramp/Goliath处理程序。
至于您关于Ruby运行时的问题,这也取决于“slow”正在执行的工作。如果您正在进行CPU密集型计算,则有可能出现GIL问题。如果您正在进行IO,则GIL不应妨碍您。(我说不应该是因为我相信我曾经读过旧mysql gem阻止GIL的问题。)

就我个人而言,我使用sinatra-synchrony作为后端混搭Web服务已经取得了成功。我可以并行地向外部Web服务发出多个请求,并等待它们全部返回。与此同时,前端Rails服务器使用线程池,并直接向后端发出请求。虽然不是完美的解决方案,但目前已经足够好用。


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