2011-11-16 77 views
8

我已经阅读了大量关于线程安全性和不同版本的ruby和rails的性能的Web上的材料,我认为在这一点上我很了解这些事情。如何部署线程安全的异步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被履行,而slow的请求正在等待IO。

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

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

+0

我怀疑你的ruby版本对于第2点会有多少发言权。这将更依赖于服务器的并发实现以及如何将rails本身放在一起。 – providence

回答

6

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

在完美的世界中,您可以使用类似于sinatra-synchrony gem的东西来处理光纤中的每个请求。你只能受限于最大数量的光纤。不幸的是,光纤上的堆栈大小为hardcoded,在Rails应用程序中很容易超载。另外,由于异步IO启动后的自动屈服,我已经阅读了一些关于调试光纤困难的恐怖故事。当使用纤维时,竞赛条件仍然是可能的。目前,纤维化的Ruby是一个贫民窟,至少在Web应用程序的前端。

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

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

也看看CrampGoliath。这些可以让你在一个单独的Rack应用中实现你的slow方法,这个应用与你的Rails应用一起被托管,但是你可能必须重写你的代码才能在Cramp/Goliath处理器中工作。

至于你关于Ruby运行时的问题,它也取决于slow正在做的工作。如果你正在做CPU计算,那么你就会冒GIL给你带来问题的风险。如果你在做IO,那么GIL 不应该是。 (我说不应该,因为我相信我已经阅读了关于阻止GIL的老式的MySQL gem的问题。)

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