InfoQ

文章

深入浅出理解BackgrounDRb插件

作者 Ezra Zygmuntowicz译者 苏锐 发布于 2007年9月17日 上午7时21分

社区
Ruby
主题
编程,
Ruby on Rails
标签
服务设计,
Rails插件,
DRb

用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

1 条回复

回复

好东西 发表人 Jerome Chen 发表于 2007年11月29日 上午1时8分
  1. 返回顶部

    好东西

    2007年11月29日 上午1时8分 发表人 Jerome Chen

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

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

独家内容

应用JSF、Ajax和Seam开发Portlets(1/3)

本文主要讲述了如何用JBoss Portlet Container 和JBoss Portlet Bridge创建新项目,怎样配置一个JSF应用去使用JBoss Portlet Bridge,以及JBoss Portlet Bridge所具备的功能。

AtomServer:数据分发的发布动力(第二部分)

在这篇文章里,Bryon Jacob和Chris Berry将和我们继续探讨AtomServer,它是基于Apache Abdera的完整Atom存储实现。作者还创建了几个Atompub规范扩展,其中包括自动标记、批处理和Feeds聚合。

架构师(试刊第二期)

InfoQ中文站的电子杂志《架构师》试刊第二期出版了!相比于上期,我们在内容的选择安排和版式上都根据读者的意见重新做了修正。“细节决定成败”,我们希望基于InfoQ中文站的专业内容,《架构师》能逐渐成为大家喜欢的电子刊物!

一种正规的性能调优方法:基于等待的调优

在本文中,Steven Haines探讨了Web应用性能调优问题。该领域过去更像是一门艺术而不是一门科学。他提出了一种称为基于等待调优的方法,使整个调优过程更加可度量,也因此更具科学性。

Java程序员ActionScript 3入门

通常来说,改变技术路线时最艰难的部分是辨别语言语法之间的不同。这篇文章就为Java开发者提供了一份如何转向Flex基础语言ActionScript的指南。

浅谈如何创建Rails应用

本视频主要以财帮子为例,介绍了如何创建一个PV为百万级的Rails应用。其中包括:Rails应用的服务器架构、Rails Cache的优化、负载均衡的处理、Web服务器的调试、分布式解决方案、Open API的设计等等。

Alexandru Popescu谈InfoQ.com网站架构

InfoQ首席架构师Alexandru Popescu在采访中谈论了InfoQ架构、Webwork与DWR、Hibernate与JCR、Hibernate可扩展性、最新的InfoQ视频流系统和InfoQ的未来规划。

揭示常见的重构误区

相对于Java,.NET在持续重构方面所给与的重视仍然少为人知,大多数人对于重构是否真正属于开发过程,以及如何将其应用到开发过程中持观望态度。Danijel Arsenovski试图为你揭示这些谜题。