homeASCIIcasts

171: Delayed Job 

(view original Railscast)

Other translations: En

Other formats:

Written by Enn (enn.javaeye.com)

Delayed_job plugin是一个健壮的稳定的用于在后台运行任务的解决方案。

下面的例子中,使用一个邮件群发的程序来做演示。如下图,点击'deliver'后,将开始发送邮件.例子中每个请求大概需要10秒的时间来执行,对使用者来说,点一个链接要等待10多秒显然太久了.

The index page of our mailings application.

像这样的执行时间较长的请求,最好的办法就是放到后台执行.接下来,使用Delayed_job来实现这个功能。

安装

官方发布的Delayed_job可以在这里找到tobi’s Github pages ,这上面有许多的分支,最好选择由collectiveidea提供的分支,这个分支功能更新也更完善,并且提供了一个生成脚本,用于创建Delayed_job需要的表。

使用下面的命令安装Delayed_job

script/plugin install git://github.com/collectiveidea/delayed_job.git  

安装完成后 运行下面的脚本创建Migration(只有collectiveidea的分支才有此功能)

script/generate delayed_job  
  create  script/delayed_job  
  exists  db/migrate  
  create  db/migrate/20090720195941_create_delayed_jobs.rb  

之后

rake db:migrate

创建delayed job的数据表

下一步,将启动任务进程。有很多种方法可以开启任务进程。如果设置应用为production模式,则可以使用上面那段代码创建的script命令来启动进程。这个命令将监视任务,并允许多个任务同时运行。但当开发的时候更好的选择是使用以下的RAKE命令来开启任务。

rake jobs:work  

由此命令开启的任务将一直运行直到我们ctrl+c取消它,便于我们开发调试。

使用delayed_job来处理邮件群发程序

现在已经安装配置好了delayed_job,该修改代码让邮件发送程序在后台运行了。下面就是在MailingController中的deliver方法

/app/controllers/mailings_controller.rb

def deliver    
  mailing = Mailing.find(params[:id])    
  mailing.deliver    
  flash[:notice] = "Mailing delivered"    
  redirect_to mailings_url    
end    

可以看到是Mailing中的deliver方法负责邮件发送功能,也就是它影响了程序的响应时间,我们使用send_later把这个方法放到后台执行。这个方法可以在任何model中以

mailing.send_later(:deliver)    

的形式请求。参数即是要在后台执行的方法名,支持多参数传递。这个方法和ruby的send方法很像,但send_later方法会把任务移动到后台的队列里执行。现在修改deliver方法来看看send_later是怎样工作的。注意,同时也修改了提示信息。

/app/controllers/mailings_controller.rb

def deliver    
   mailing = Mailing.find(params[:id])    
   mailing.send_later(:deliver)    
   flash[:notice] = "Mailing is being delivered."    
   redirect_to mailings_url    
end    

现在,点击deliver后立即就可以看到响应信息。

The deliver link now returns a response straightaway.

稍候几秒刷新页面,就可以看到第二封邮件也发送出去了

The second mailing is shown as delivered.

在运行了rake jobs:work的情况下 可以在终端窗口中看到如下的任务信息

1 jobs processed at 0.0993 j/s, 0 failed ...  

创建一个自定义类

send_later方法使得任务在后台运行变得很简单,如果想要做更多就需要创建一个专用于后台执行代码的类。在/lib 目录下新建一个ruby文件"mailing_job.rb"下面是相关代码

/lib/mailing_job.rb

class MailingJob < Struct.new(:mailing_id)    
   def perform    
     mailing = Mailing.find(mailing_id)    
     mailing.deliver    
   end    
end    

这个类中必须要有一个无参的perform方法,这使得需要一个以mailing_id作为参数的初始化方法来传递需要被发送的邮件的mailing_id。 习惯上在用delayed_job的时候会让类继承struct,这样可以自由定义我们想要类拥有的属性。现在,初始化MailingJob的时候mailing_id会作为第一个参数,这恰好提供了mailing_id,而不需要创建实例变量或是初始化方法。

现在,MailingController是这个样子的

/app/controllers/mailings_controller.rb

    
def deliver    
  Delayed::Job.enqueue(MailingJob.new(params[:id]))    
  flash[:notice] = "Mailing is being delivered."    
  redirect_to mailings_url    
end  

方法里的第一句包揽了所有工作:以想要发送的邮件的id作为参数创建MailingJob实例对,并把它加入任务队列。

使用自定义类允许我们在传递附加参数给Delayed::Job.enqueue的时候利用一些delayed_job的特性。第一个参数用于设置优先级,这个参数默认值为0,所以如果传一个更高的值过去,这个任务将会优先于那些使用默认优先级的任务执行。

Delayed::Job.enqueue(MailingJob.new(params[:id]), 3)    

同理,设定一个更低的值将使得该任务延后于默认优先级的任务执行。

 
Delayed::Job.enqueue(MailingJob.new(params[:id]), -3)    

第二个参数用于设定任务的启动时间

Delayed::Job.enqueue(MailingJob.new(params[:id]), 3, 3.days.from_now)   

这样设置后,3天之内这个任务将不被视为在任务队列中。

自动重试

delayed_job最后一个值得提及的特性就是自动重试功能。如果一个方法抛出了异常,这个异常会被捕获,之后这个方法将会重新运行。这个过程最多重复25次,次数越多重试的时间间隔就越长。需要特别注意的是,在这个邮件发送程序中,如果邮件发送中途出错,应该确认是从发送失败的那封邮件开始重试而不是整个邮件列表重新发送。

这些就是delayed_job的简单介绍。正如我们看到的,这已经是一个功能相当完整的插件,最好的创建后台执行任务的应用之一