BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

深入浅出理解BackgrounDRb插件

| 作者 Ezra Zygmuntowicz 关注 0 他的粉丝 ,译者 苏锐 关注 0 他的粉丝 发布于 2007年9月18日. 估计阅读时间: 14 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

用Ruby on Rails开发各种类型的Web应用确实是很棒的选择,但是这些Web应用所在的问题领域中,你可能经常会遇到一些复杂精密的计算或者长时间运行的后台任务。但是由于你的Web应用被限制在 HTTP协议的request/response模型下,这可能就会造成一些问题。你知道应该如何运行漫长的后台任务而不让你的Web服务器超时么?你又知道该如何把这些任务的进度告诉用户么?

作者写了一个叫做BackgrounDRb的Rails插件用来解决上面的问题。在Ruby的标准库中已经预制了DRb(Distributed Ruby),为使用TCP/IP或Unix sockets通过网络存取Ruby对象提供了一个简单的API。BackgrounDRb提供了一个框架方便在Rails以外的独立线程中运行后台任务,从而摆脱了request/response模型。而且使用DRb你可以在Rails中使用钩子函数为用户提供任务进度或者状态更新。

BackgrounDRb服务端程序通过发布一个MiddleMan对象来管理你所有的woker类,其中包括一个由{job_key => running_worker_object}键值对组成的@jobs和一个由{job_key => timestamp}键值对组成的@timestamps两个hash,MiddleMan对象是DRb服务器和你的Rails应用之间的一个接口。下面的图表简单说明了BackgrounDRb和Rails应用之间的关系。

下面是通过插件提供的worker generator脚本生成的一个worker类。

$ script/generate worker Foo
class FooWorker < BackgrounDRb::Rails
def do_work(args)
# This method is called in its own new thread when you
# call new worker. args is set to :args
end

end

当FooWorker对象在Rails中通过MiddleMan初始化以后,do_work方法会自动运行在它自己的线程中。由于do_work在自己的线程中运行,所以Rails不需要等待do_work完成就可以继续执行。

使用BackgrounDRb,你经常会通过AJAX请求创建一个新的worker对象。在View中可以使用periodically_call_remote来取得任务的进度,再用你喜欢的方式展现给用户。接下来让我们补全刚才的 FooWorker类,并告诉你如何在一个rails controller中创建新的FooWorker对象并获取它的进度。

class FooWorker < BackgrounDRb::Rails
attr_reader :progress
def do_work(args)
@progress = 0
calculate_the_meaning_of_life(args)
end
def calculate_the_meaning_of_life(args)
while @progress < 100
# calculations here
@progress += 1
end
end
end

在controller中添加下面的代码:

class MyController < ApplicationController
def start_background_task
session[:job_key] =
MiddleMan.new_worker(:class => :foo_worker,
:args => "Arguments used to instantiate a new FooWorker object")
end
def get_progress
if request.xhr?
progress_percent = MiddleMan.get_worker(session[:job_key]).progress
render :update do |page|
page.call('progressPercent', 'progressbar', progress_percent)
page.redirect_to( :action => 'done') if progress_percent >= 100
end
else
redirect_to :action => 'index'
end
end
def done
render :text => "

Your FooWorker task has completed

"
MiddleMan.delete_worker(session[:job_key])
end
end

再将下面的代码添加到你的start_background_task.rhtml视图中:









 

<%= periodically_call_remote(:url => {:action =>
'get_progress'}, :frequency => 1) %>

MiddleMan.new_worker方法会随机产生一个job_key,你可以把它存在session中方便存取。如果你想指定job_key的名字可以使用下面的方法:

 # This will throw a BackgrounDRbDuplicateKeyError if the :job_key already exists.
MiddleMan.new_worker(:class => :foo_worker,
:job_key => :my_worker,
:args => "Arguments used to instantiate a new FooWorker object")

MiddleMan.get_worker :my_worker

BackgrounDRb安装之后还会生成一个配置文件RAILS_ROOT/config/backgroundrb.yml。里面有一个load_rails配置选项,如果设置为true你就可以在worker class中使用你的ActiveRecord对象了,在BackgrounDRb服务启动的时候会自动根据database.yml中的设置去访问数据库。

这个插件还可以用于缓存类似ActiveRecord object这类大对象或者需要大量计算的对象,你也可以把渲染后的View对象或者大的查询进行缓存,事实上你可以缓存任何文本和任何可以被序列华的对象。下面是一个使用缓存的例子:

# Fill the cache
@posts = Post.find(:all, :include => :comments)
MiddleMan.cache_as(:post_cache, @posts)
# OR
@posts = MiddleMan.cache_as :post_cache do
Post.find(:all, :include => :comments)
end

# Retrieve the cache
@posts = MiddleMan.cache_get(:post_cache)
# OR
@posts = MiddleMan.cache_get(:post_cache) { Post.find(:all, :include => :comments) }

MiddleMan.cache_get接受一个可选的block,如果缓存中的:post_cache是空的,block中的计算结果就会被放到cache中并赋给@post。 如果你没有提供block而且缓存是空的则返回nil。

在现在的实现中,你要自己负责对缓存过期,删除worker对象。有两种方法,一种是直接调用MiddleMan.delete_worker(:job_key)或者MiddleMan.delete_cache(:cache_key),也可以将一个时间对象传给MiddleMan.gc! ,删除所有在timestamp之前的jobs(文章开始提到MiddleMan包括@jobs和@timestamps两个hash)。下面的脚本可以删除30分钟以前的jobs,你可以把它放在cron中执行:

#!/usr/bin/env ruby
require "drb"
DRb.start_service
MiddleMan = DRbObject.new(nil, "druby://localhost:22222")
MiddleMan.gc!(Time.now - 60*30)

在最新的特性中会有一个定时机制加入到BackgrounDRb中,这将允许你定时的运行你自己的任务和垃圾回收,或者在你创建一个新的job或cache的时候就定义一个存活时间的参数。

插件中还包含了一些命令行脚本用于启动/停止 BackgrounDRb,在OS X、Linux或者BSD上面可以使用rake:

$ rake backgroundrb:start
$ rake backgroundrb:stop

在Windows上当你运行BackgrounDRb服务的时候要始终打开那个启动服务的命令行窗口(希望后面的版本可以有所改进)。所以在Windows上启动BackgrounDRb服务你要先打开一个命令行窗口,然后运行下面的命令:

> ruby script\backgroundrb\start
# ctrl-break to stop

现在你可能会问这东西在现实中究竟可以用在什么地方?在下面的列表中作者告诉了你,他正在用BackgrounDRb做什么:

  • 下载并缓存RSS,这样可以做一个RSS 聚合器。
  • 使用watir驱动浏览器在后来访问网站并收集信息,做自动的屏幕抓取。
  • Xen VPS的自动创建和系统管理任务。
  • 后台为Hyper Estraier和 erret创建索引。
  • 连接Rails和IRC机器人。

作者在后续版本中还计划加入创建新进程的能力,以便能处理需要Ruby解释器实例的更大的任务。在Windows上希望可以作为service运行,希望熟悉Windows service的人能提供一些帮助,任何建议和补丁都非常欢迎。

查看英文原文:Introduction to BackgrounDRb
作者简介:苏锐,Ruby on Rails开发者,关注各种Web开发技术,Mac爱好者。他的博客为:http://www.surui.net。参与InfoQ中文站内容建设,请邮件至china-editorial@infoq.com

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

好东西 by Chen Jerome

东西不错,不过应用场景不是特别大,对实时性要求不高的东西根本用不上,在*nix下可以用cron,win下可以用at来定时执行指定的任务,基本上可以满足要求了。

------------------------------------------
[Ruby中文社区] - ruby-lang.org.cn

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

1 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT