庇护祝福的分享

Be worthy

Rails中间层解决方案

调用API构建前端

一般情况下,rails应用都是从数据库进行数据获取,而采用API调用的话,就不再需要model层了,只需要在控制器中用http库进行接口调用就可以了,大大简化了结构。 example:

require 'open-uri'
require 'json'
require 'net/http'
def index  
  uri = URI("http://test.csdn.net/api/format/json")
  req = Net::HTTP.get(uri)
  @slides = JSON.parse(req)["data"]["contents"]
end

问题:多个API调用时的阻塞问题

def index  
  uri_one = URI("http://test.csdn.net/api/1/format/json")
  uri_two = URI("http://test.csdn.net/api/2/format/json")
  uri_three = URI("http://test.csdn.net/api/3/format/json")
  ......
  req_one = Net::HTTP.get(uri_one)
  req_two = Net::HTTP.get(uri_two)
  req_three = Net::HTTP.get(uri_three)
  ......
  @slides = JSON.parse(req)["data"]["contents"]
  ......
end

如果有10个API调用,每个Net::HTTP.get(uri)通信耗时30ms,那么就是300ms的时间。

解决方案一:多线程处理

threads = []
threads << Thread.new {@req_one = Net::HTTP.get(uri_one)}
threads << Thread.new {@req_two = Net::HTTP.get(uri_two)}
threads << Thread.new {@req_three = Net::HTTP.get(uri_three)}
......
threads.each { |t| t.join }
data = JSON.parse(@req_one)["data"]

多进程是抢占资源的一个模型,并非并行计算,比如线程1执行一个http通信后中间有30ms的I/O时间,那么这段阻塞的时间就会被线程2抢占。 开启多线程并行处理,可以很大程度上提高处理速度。但是有两个问题: 一、开多个线程以及线程间的切换造成资源开销增加,实际上降低了应用的吞吐能力。 二、由于Ruby存在GIL (Global Interpreter Lock),并不能真正利用多线程进行并行计算。(JRuby 去除了 GIL,是真正意义的多线程,既能应付 IO Block,也能充分利用多核 CPU 加快整体运算速度。)

解决方案二:采用异步模型处理

Ruby的异步解决方案:Eventmachine

在Gemfile中添加:

gem 'eventmachine'
gem 'em-http-request', :require => 'em-http'

控制器中的代码:

EventMachine.run {
  http = EM::HttpRequest.new("http://test.csdn.net/api/1/format/json").get
  http.errback { 
    render :text "接口连接失败"; 
    EM.stop 
  }
  http.callback {
    req = http.response
    @slides = JSON.parse(req)["data"]["list"]
    @page_count = JSON.parse(req)["data"]["count"].to_i/18+1
    EM.stop
  }
}

这种方式不仅解决了阻塞问题,而且可以有效的提高系统的吞吐能力。但带来的问题是代码量增加,复杂逻辑下代码的复杂度成倍增加,不利于开发和维护。

引入纤程(Fiber),用同步代码编写异步程序

在Gemfile中添加gem 'em-synchrony'。 控制器代码:

EM.synchrony do
  http = EM::HttpRequest.new("http://test.csdn.net/api/1/format/json").get
  req = http.response
  @slides = JSON.parse(req)["data"]["list"]
  EM.stop
end

多个接口调用实例:

require "em-synchrony"
require "em-synchrony/em-http"
require "em-synchrony/fiber_iterator"
EM.synchrony do
  concurrency = 2
  urls = ['http://url.1.com', 'http://url2.com']
  results = []
  EM::Synchrony::FiberIterator.new(urls, concurrency).each do |url|
    res = EventMachine::HttpRequest.new(url).get
    results << res.response
  end
  puts results # all completed requests
  EventMachine.stop
end