原帖这里:http://www.igvita.com/2008/05/27/ruby-eventmachine-the-speed-demon/
由于译者能力有限,有错误或不足的地方还请见谅。
reactor是一种事件设计模式,并发处理提交给一个service handler的多个请求,当请求抵达后,service handler采用多路复用方式同步地分配这些请求至相关的request handlers。
为什么要从Reactor开始?
我们测试了很多不同的web-server架构,发现性能最好的总是采用事件驱动的服务。在一个并发处理成千上万请求的环境中,复制和上下文切换会导致产生更大的开销(fork的性能最差,它每次会在父进程上进行内存复制)。相比之下,一个紧密、高度优化的事件循环能够在高负载情况下表现出很好的性能。
EventMachine and Reactor pattern
近期听了很多ruby应用服务器替代方案的演讲,我得到了一个一致的意见:“事件驱动能够很好的处理轻量的请求,如果你有一个持续事件很长的请求,事件驱动将表现的非常差。”从技术上讲,有效,但在实践中,不一定是正确的。让我们先从一个最简单的例子开始:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
require 'rubygems' require 'eventmachine' require 'evma_httpserver' class Handler < EventMachine::Connection include EventMachine::HttpServer def process_http_request resp = EventMachine::DelegatedHttpResponse.new( self ) sleep 2 # Simulate a long running request resp.status = 200 resp.content = "Hello World!" resp.send_response end end EventMachine::run { EventMachine::start_server("0.0.0.0", 8080, Handler) puts "Listening..." } # Benchmarking results: # # > ab -c 5 -n 10 "http://127.0.0.1:8080/" # > Concurrency Level: 5 # > Time taken for tests: 20.6246 seconds # > Complete requests: 10 |
这里我们使用eventmachine完成了一个最简单的http web-server。使用ab进行压力测试,并发5(-c 5),10个请求 ,结果处理时间用了20秒左右。和期望的一样,reactor同步方式处理每个请求,并发为1,10个请求,2秒处理一个 。
EventMachine: Reactor使用轻量并发?
在上个例子中,reactor 的同步性质造成了瓶颈,这也是EM脱离纯理论方式的地方。需要特别说明的,EM提供一种机制可以分配请求到一个线程池去处理(默认20个线程)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
require 'rubygems' require 'eventmachine' require 'evma_httpserver' class Handler < EventMachine::Connection include EventMachine::HttpServer def process_http_request resp = EventMachine::DelegatedHttpResponse.new( self ) # Block which fulfills the request operation = proc do sleep 2 # simulate a long running request resp.status = 200 resp.content = "Hello World!" end # Callback block to execute once the request is fulfilled callback = proc do |res| resp.send_response end # Let the thread pool (20 Ruby threads) handle request EM.defer(operation, callback) end end EventMachine::run { EventMachine::start_server("0.0.0.0", 8081, Handler) puts "Listening..." } # Benchmarking results: # # > ab -c 5 -n 10 "http://127.0.0.1:8081/" # > Concurrency Level: 5 # > Time taken for tests: 4.21405 seconds # > Complete requests: 10 |
测试一下,一共10个请求,并发为5,结果处理时间大约为4秒左右!说明我们的服务在并行处理请求,类似mongrel web-server。
Deferrable: 不使用线程的并发
Eventmachine从twisted(python事件驱动网络编程框架)借鉴很多,也提供一个deferrable模块,在某些情况下允许我们获得并发处理的优势,但没有任何线程开销! Deferrable允许你配置任意数量的Ruby代码块(callbacks或errbacks),当对应的Deferrable的对象状态在未来某个时刻发生改变时回调执行。想象一下你正在实现一个http server,但你需要远程调用一些其他的服务才能返回一个完整的客户请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
require 'rubygems' require 'eventmachine' require 'evma_httpserver' class Handler < EventMachine::Connection include EventMachine::HttpServer def process_http_request resp = EventMachine::DelegatedHttpResponse.new( self ) # query our threaded server (max concurrency: 20) http = EM::Protocols::HttpClient.request(:host=>"localhost", :port=>8081, :request=>"/" ) # once download is complete, send it to client http.callback do |r| resp.status = 200 resp.content = r[:content] resp.send_response end end end EventMachine::run { EventMachine::start_server("0.0.0.0", 8082, Handler) puts "Listening..." } # Benchmarking results: # # > ab -c 20 -n 40 "http://127.0.0.1:8082/" # > Concurrency Level: 20 # > Time taken for tests: 4.41321 seconds # > Complete requests: 40 |
没有线程,仍然可以在4秒左右完成40个请求的处理,唯一的限制是我们实现的上个例子,最多可以并发处理20个请求。这是eventmachine的美妙之处:如果你构造的work可以defer或阻塞在socket,reactor将循环继续处理其他的请求。当defer的工作完成后,它会产生一个成功的消息,reactor进行回调处理。没有使用线程,没有同步处理请求,Dnsruby是一个非常棒的例子,可以进一步学习这种模式