最近翻了下电脑里几年前记得一些东西, 想把关于 ruby 事件驱动编程的一些知识重新整理下并分享出来
为什么需要事件驱动编程
-
关于阻塞
服务器程序开发的时候, 通常和一些外部服务做通信, 比如 数据库服务器 外部接口的请求.
在常规的编程模式下, 比如程序做一次数据库查询的时候, 先需要向数据库发起查询请求, 通常要等待一段数据库执行sql时间, 只有执行结果返回给程序之后, 程序才能执行接下来的任务.
而这段等待的时间里, 程序啥也做不了, 只能干等着; 那这段时间, 服务器的cpu资源就闲置了. 这就是程序被阻塞了.
而大部分的 web 程序, 在处理一次 web 请求中, 通常会被很多外部服务的 IO 操作所阻塞. 很有可能程序处理一次请求的时间是100ms, 但是99%的时间, 程序都在等待 sql 服务器返回结果, 或者是等待外部接口请求返回结果.
-
阻塞的解决办法
那怎么让 cpu 在程序被阻塞的时候有事可干呢
通常有三种办法:
- 多进程: 最简单粗暴的一种方法, 多开一些进程, 每个进程一次只处理一个请求, 可以使用unicorn做多进程服务器
- 优点是实现简单( 只需要web 程序多开一些进程, 不需要再做其他的开发工作), 安全可靠.
- 缺点是内存占用高(每个进程之间内存不共享), 效率低(每个进程的执行权需要靠系统来分配, 进程切换的占用的资源开销大).
- 多线程: 和多进程类似, 进程变为线程, 最近比较火的puma就是多线程服务器
- 优点: 相比多进程, 内存占用低(线程之间可以共享内存), 效率比多进程要高, 线程切换效率比进程高
- 缺点: 所有代码都需要考虑线程安全问题, 出bug 很难调试, 很难保证所有引入的库都是线程安全
- 基于事件驱动的异步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中的代码.