基于事件驱动的异步IO (ruby) 系列一

2017-01-04  本文已影响287人  这哥们没昵称

最近翻了下电脑里几年前记得一些东西, 想把关于 ruby 事件驱动编程的一些知识重新整理下并分享出来

为什么需要事件驱动编程

服务器程序开发的时候, 通常和一些外部服务做通信, 比如 数据库服务器 外部接口的请求.
在常规的编程模式下, 比如程序做一次数据库查询的时候, 先需要向数据库发起查询请求, 通常要等待一段数据库执行sql时间, 只有执行结果返回给程序之后, 程序才能执行接下来的任务.
而这段等待的时间里, 程序啥也做不了, 只能干等着; 那这段时间, 服务器的cpu资源就闲置了. 这就是程序被阻塞了.
而大部分的 web 程序, 在处理一次 web 请求中, 通常会被很多外部服务的 IO 操作所阻塞. 很有可能程序处理一次请求的时间是100ms, 但是99%的时间, 程序都在等待 sql 服务器返回结果, 或者是等待外部接口请求返回结果.

那怎么让 cpu 在程序被阻塞的时候有事可干呢
通常有三种办法:

  1. 多进程: 最简单粗暴的一种方法, 多开一些进程, 每个进程一次只处理一个请求, 可以使用unicorn做多进程服务器
    • 优点是实现简单( 只需要web 程序多开一些进程, 不需要再做其他的开发工作), 安全可靠.
    • 缺点是内存占用高(每个进程之间内存不共享), 效率低(每个进程的执行权需要靠系统来分配, 进程切换的占用的资源开销大).
  2. 多线程: 和多进程类似, 进程变为线程, 最近比较火的puma就是多线程服务器
    • 优点: 相比多进程, 内存占用低(线程之间可以共享内存), 效率比多进程要高, 线程切换效率比进程高
    • 缺点: 所有代码都需要考虑线程安全问题, 出bug 很难调试, 很难保证所有引入的库都是线程安全
  3. 基于事件驱动的异步IO模式
    在高并发, 对性能要求非常高的情况下, 多进程
    /线程的效率都不够高, 需要在代码层来控制阻塞时cpu的切换, 这时候就需要引入新的模式: 异步编程模式.

同步与异步

拿一段常见的 Rails 代码做实例:

  def show
    @post = Post.find(params[:id]) #数据库查询post
    @post.update(last_view_time: Time.now) #更新post
     ...
  end

在执行 Post.find(params[:id]) 时, 程序其实是被阻塞了, 在查询完成之后才会执行后面的代码, 这就是常规的同步模式.

异步模式: 当程序处理请求遇到阻塞的操作时(比如数据库查询), 把后面需要执行的代码放到callback中, 然后直接去处理下一个请求, 直到数据查询完成之后, 再去执行之前的callback代码. 以下是一段ruby异步模式的代码

require "em-synchrony"
require "em-synchrony/em-http"
EM.synchrony do
   concurrency = 2 
   urls = ['http://url.1.com', 'http://url2.com']
   results = EM::Synchrony::Iterator.new(urls, concurrency).map do |url, iter|
       http = EventMachine::HttpRequest.new(url).aget
       http.callback { iter.return(http) } 
       http.errback { iter.return(http) } 
   end 
   p results
   EventMachine.stop
end  

EventMachine::HttpRequest.new(url).aget 是执行异步的http请求, 请求完成后的代码放到了callback中, 程序不会等待 http 请求完成, 而是直接去执行迭代器Iterator中的下个任务. 等http请求完成时才会去执行callback中的代码.

上一篇 下一篇

猜你喜欢

热点阅读