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

时间:2011-11-16 04:43:06

标签: ruby-on-rails ruby multithreading rack thin

我已经在网上阅读了大量关于不同版本的ruby和rails中的线程安全性和性能的材料,我想我现在对这些事情了解得很清楚。

讨论中似乎奇怪的是如何实际部署异步Rails应用程序。在谈论应用程序中的线程和同步性时,人们想要优化两件事:

  1. 利用所有CPU内核,使用最少的RAM
  2. 能够在先前的请求等待IO时提供新请求
  3. 第1点是人们(正确地)对JRuby感到兴奋的地方。对于这个问题,我只是想优化第2点。

    说这是我应用中唯一的控制器:

    TheController < ActionController::Base
      def fast
        render :text => "hello"
      end
    
      def slow
        render :text => User.count.to_s
      end
    end
    

    fast没有IO,每秒可以提供数百或数千个请求,slow必须通过网络发送请求,等待工作完成,然后通过网络,因此比fast慢得多。

    因此,当fast的请求等待IO时,理想的部署将允许满足slow的数百个请求。

    围绕Web的讨论似乎缺少的是堆栈的哪一层负责启用此并发。 thin有一个--threaded标志,它将“在线程中调用机架应用程序[实验]” - 是否为每个传入请求启动一个新线程?在持久存在并等待传入​​请求的线程中假装机架应用程序实例?

    是唯一的方式还是其他方式? ruby运行时是否对优化点2很重要?

1 个答案:

答案 0 :(得分:6)

适合您的方法在很大程度上取决于您的slow方法正在做什么。

在完美的世界中,您可以使用像sinatra-synchrony gem这样的东西来处理光纤中的每个请求。您只受最大光纤数量的限制。不幸的是,光纤上的堆栈大小是hardcoded,并且很容易在Rails应用程序中溢出。另外,我已经阅读了一些关于调试光纤困难的恐怖故事,因为异步IO启动后会自动产生。使用纤维时仍然可以使用比赛条件。目前,纤维化的Ruby有点像贫民窟,至少在网络应用程序的前端。

不需要更改代码的更实用的解决方案是使用具有工作线程池的Rack服务器,例如Rainbows!还是彪马。我相信Thin的--threaded标志处理新线程中的每个请求,但是启动本机OS线程并不便宜。最好使用池大小设置得足够高的线程池。在Rails中,不要忘记在生产中设置config.threadsafe!

如果您可以更改代码,可以查看Konstantin Haase的优秀talk on real-time Rack。他讨论了使用EventMachine::Deferrable类在Rack构建的传统请求/响应周期之外产生响应。这看起来很整洁,但你必须以异步方式重写代码。

另请查看CrampGoliath。这些允许您在与Rails应用程序一起托管的单独Rack应用程序中实现slow方法,但您可能还必须重写代码以在Cramp / Goliath处理程序中工作。

至于关于Ruby运行时的问题,它还取决于slow正在做的工作。如果你正在进行CPU大量计算,那么你冒着GIL的风险给你带来问题。如果你正在做IO,那么GIL 不应该妨碍你。 (我说不应该因为我相信我已经阅读过关于阻止GIL的旧版mysql gem的问题。)

就个人而言,我已成功使用sinatra-synchrony作为后端,mashup Web服务。我可以并行地向外部Web服务发出多个请求,并等待所有请求返回。同时,前端Rails服务器使用线程池,并直接向后端发出请求。不完美,但现在它运作良好。