在同一个EventMachine中处理Sinatra和Faye

时间:2016-01-14 23:58:32

标签: ruby sinatra thin eventmachine faye

我正在编写一个同时使用Sinatra的Web应用程序 - 用于一般的单客户端同步get-和Faye - 用于基于多客户端异步服务器的广播。

我对({3}}的理解是因为它允许我将这两者放在一个进程中并为我处理并行请求。但是,我的测试表明,如果Sinatra或Faye需要花费很长时间来处理某个特定的动作(这可能会在我的实际应用程序中定期发生),它会阻止另一个动作。

如何重写下面的简单测试应用程序,以便在取消注释sleep命令时,Faye-push和AJAX轮询响应不会延迟?

%w[eventmachine thin sinatra faye json].each{ |lib| require lib }

def run!
  EM.run do
    Faye::WebSocket.load_adapter('thin')

    webapp = MyWebApp.new
    server = Faye::RackAdapter.new(mount:'/', timeout:25)

    dispatch = Rack::Builder.app do
      map('/'){     run webapp }
      map('/faye'){ run server }
    end

    Rack::Server.start({
      app:     dispatch,
      Host:    '0.0.0.0',
      Port:    8090,
      server:  'thin',
      signals: false,
    })
  end
end

class MyWebApp < Sinatra::Application
  # http://stackoverflow.com/q/10881594/405017
  configure{ set threaded:false }

  def initialize
    super
    @faye = Faye::Client.new("http://localhost:8090/faye")
    EM.add_periodic_timer(0.5) do
      # uncommenting the following line should not
      # prevent Sinatra from responding to "pull"
      # sleep 5
      @faye.publish( '/push', { faye:Time.now.to_f } )
    end
  end

  get ('/pull') do
    # uncommenting the following line should not
    # prevent Faye from sending "push" updates rapidly
    # sleep 5
    content_type :json
    { sinatra:Time.now.to_f }.to_json
  end

  get '/' do
    "<!DOCTYPE html>
    <html lang='en'><head>
      <meta charset='utf-8'>
      <title>PerfTest</title>
      <script src='https://code.jquery.com/jquery-2.2.0.min.js'></script>
      <script src='/faye/client.js'></script>
      <script>
      var faye = new Faye.Client('/faye', { retry:2, timeout:10 } );
      faye.subscribe('/push',console.log.bind(console));
      setInterval(function(){
        $.get('/pull',console.log.bind(console))
      }, 500 );
      </script>
    </head><body>
      Check the logs, yo.
    </body></html>"
  end
end

run!

1 个答案:

答案 0 :(得分:1)

  

睡眠与999999.times {Math.sqrt(rand)}或exec(“sleep 5”)有何不同?那些也阻止任何单线程,对吧?这就是我想要模拟的,一个需要很长时间的阻塞命令。

这两种情况都会阻塞您的reactor /事件队列。使用 reactor模式,您希望避免任何CPU密集型工作,并且完全专注于IO(即网络编程)。

单线程反应器模式与I / O协同工作的原因是因为IO不是CPU密集型 - 而是在系统内核处理I / O请求时阻塞程序。

反应器模式通过立即切换单个线程来潜在地处理不同的事情(可能是其他一些请求的响应已经完成),直到I / O操作完成后才能利用这一点。 OS。

一旦操作系统收到您的IO请求的结果,EventMachine就会找到您最初在I / O请求中注册的回调,并将响应数据传递给它。

所以不是像

那样的东西
# block here for perhaps 50 ms
r = RestClient.get("http://www.google.ca") 
puts r.body

EventMachine更像是

# Absolutely no blocking
response = EventMachine::HttpRequest.new('http://google.ca/').get

# add to event queue for when kernel eventually delivers result
response.callback {
    puts http.response
}

在第一个示例中,您需要Web服务器的多线程模型,因为发出网络请求的单个线程可能会阻塞可能的秒数。

在第二个示例中,您没有阻塞操作,因此一个线程运行良好(并且通常比多线程应用程序更快!)

如果你曾经有过CPU密集型操作,EventMachine允许你稍微作弊,并启动一个新线程,以便反应堆不会阻塞。在此处详细了解EM.defer

最后要注意的是,这就是Node.js如此受欢迎的原因。对于Ruby,我们需要EventMachine +兼容的库用于reactor模式(例如,不能仅使用阻塞RestClient),但Node.js及其所有库都是从反应堆设计模式的开头编写的(它们是基于回调的。)