[英]polling with delayed_job
我有一个过程,通常需要几秒钟才能完成,因此我尝试使用delayed_job来异步处理它。 工作本身运作正常,我的问题是如何轮询工作以确定是否已完成。
我可以通过简单地将它分配给变量来获取delayed_job的id:
job = Available.delay.dosomething(:var => 1234)
+------+----------+----------+------------+------------+-------------+-----------+-----------+-----------+------------+-------------+
| id | priority | attempts | handler | last_error | run_at | locked_at | failed_at | locked_by | created_at | updated_at |
+------+----------+----------+------------+------------+-------------+-----------+-----------+-----------+------------+-------------+
| 4037 | 0 | 0 | --- !ru... | | 2011-04-... | | | | 2011-04... | 2011-04-... |
+------+----------+----------+------------+------------+-------------+-----------+-----------+-----------+------------+-------------+
但是一旦完成作业,它就会删除它,并且搜索完成的记录会返回错误:
@job=Delayed::Job.find(4037)
ActiveRecord::RecordNotFound: Couldn't find Delayed::Backend::ActiveRecord::Job with ID=4037
@job= Delayed::Job.exists?(params[:id])
我是否需要改变这一点,并推迟删除完整的记录? 我不知道我怎么能得到它的状态通知。 或者正在查看死记录作为完成证明吗? 其他人面对类似的事情?
让我们从API开始吧。 我想要像下面这样的东西。
@available.working? # => true or false, so we know it's running
@available.finished? # => true or false, so we know it's finished (already ran)
现在让我们写下这份工作。
class AwesomeJob < Struct.new(:options)
def perform
do_something_with(options[:var])
end
end
到现在为止还挺好。 我们有一份工作。 现在让我们编写将其排列的逻辑。 由于Available是负责这项工作的模型,让我们教它如何开始这项工作。
class Available < ActiveRecord::Base
def start_working!
Delayed::Job.enqueue(AwesomeJob.new(options))
end
def working?
# not sure what to put here yet
end
def finished?
# not sure what to put here yet
end
end
那么我们如何知道这项工作是否有效? 有几种方法,但在rails中,我觉得正确的是,当我的模型创建某些东西时,它通常与那些东西相关联。 我们如何联想? 在数据库中使用id。 让我们在Available模型上添加一个job_id
。
虽然我们正在努力,但我们怎么知道这项工作因为已经完成而无法工作,或者因为它还没有开始? 一种方法是实际检查作业实际上做了什么。 如果它创建了一个文件,请检查文件是否存在。 如果计算了一个值,请检查结果是否已写入。 有些工作并不容易检查,因为他们的工作可能没有明确的可验证结果。 对于这种情况,您可以在模型中使用标志或时间戳。 假设这是我们的情况,让我们添加一个job_finished_at
时间戳来区分尚未运行的作业和已经完成的作业。
class AddJobIdToAvailable < ActiveRecord::Migration
def self.up
add_column :available, :job_id, :integer
add_column :available, :job_finished_at, :datetime
end
def self.down
remove_column :available, :job_id
remove_column :available, :job_finished_at
end
end
好的。 现在让我们联想实际Available
,通过修改只要我们排队的工作,其工作start_working!
方法。
def start_working!
job = Delayed::Job.enqueue(AwesomeJob.new(options))
update_attribute(:job_id, job.id)
end
大。 在这一点上,我可以编写belongs_to :job
,但我们并不真的需要它。
那么现在我们知道如何编写working?
方法,这么容易。
def working?
job_id.present?
end
但是我们如何标记完成的工作呢? 没有人知道工作比工作本身更好。 因此,让我们将available_id
传递给作业(作为其中一个选项)并在作业中使用它。 为此,我们需要修改start_working!
传递id的方法。
def start_working!
job = Delayed::Job.enqueue(AwesomeJob.new(options.merge(:available_id => id))
update_attribute(:job_id, job.id)
end
我们应该将逻辑添加到作业中,以便在完成后更新job_finished_at
时间戳。
class AwesomeJob < Struct.new(:options)
def perform
available = Available.find(options[:available_id])
do_something_with(options[:var])
# Depending on whether you consider an error'ed job to be finished
# you may want to put this under an ensure. This way the job
# will be deemed finished even if it error'ed out.
available.update_attribute(:job_finished_at, Time.current)
end
end
有了这个代码,我们知道如何编写finished?
方法。
def finished?
job_finished_at.present?
end
我们已经完成了。 现在我们可以简单地对@available.working?
轮询@available.working?
和@available.finished?
此外,通过检查@available.job_id
,您可以方便地了解为您的可用作业创建了哪个确切的作业。 您可以通过说belongs_to :job
轻松将其转换为真正的关联。
我最终使用了Delayed_Job和after(job)回调的组合,它使用与创建的作业相同的ID填充memcached对象。 这样,我最小化了数据库询问作业状态的次数,而不是轮询memcached对象。 它包含我完成的作业所需的整个对象,所以我甚至没有往返请求。 我从github的一篇文章中得到了这个想法,他们做了几乎相同的事情。
https://github.com/blog/467-smart-js-polling
并使用jquery插件进行轮询,轮询次数较少,并在经过一定次数的重试后放弃
https://github.com/jeremyw/jquery-smart-poll
似乎工作得很好。
def after(job)
prices = Room.prices.where("space_id = ? AND bookdate BETWEEN ? AND ?", space_id.to_i, date_from, date_to).to_a
Rails.cache.fetch(job.id) do
bed = Bed.new(:space_id => space_id, :date_from => date_from, :date_to => date_to, :prices => prices)
end
end
我认为最好的方法是使用delayed_job中提供的回调。 这些是:成功,:错误和:之后。 所以你可以在你的模型中添加一些代码:
class ToBeDelayed
def perform
# do something
end
def after(job)
# do something
end
end
因为如果你坚持使用obj.delayed.method,那么你将不得不修补Delayed :: PerformableMethod并after
那里添加after
方法。 恕我直言,它远比轮询某些可能甚至特定于后端的值更好(例如,ActiveRecord与Mongoid)。
实现此目的的最简单方法是将您的轮询操作更改为类似于以下内容:
def poll
@job = Delayed::Job.find_by_id(params[:job_id])
if @job.nil?
# The job has completed and is no longer in the database.
else
if @job.last_error.nil?
# The job is still in the queue and has not been run.
else
# The job has encountered an error.
end
end
end
为什么这样做? 当Delayed::Job
从队列中运行作业时, 如果成功 ,它将从数据库中删除它。 如果作业失败,则记录将保留在队列中以便稍后再次运行,并且last_error
属性将设置为遇到的错误。 使用上面的两个功能,您可以检查已删除的记录以查看它们是否成功。
上述方法的好处是:
您可以通过执行以下操作将此功能封装在模型方法中:
# Include this in your initializers somewhere
class Queue < Delayed::Job
def self.status(id)
self.find_by_id(id).nil? ? "success" : (job.last_error.nil? ? "queued" : "failure")
end
end
# Use this method in your poll method like so:
def poll
status = Queue.status(params[:id])
if status == "success"
# Success, notify the user!
elsif status == "failure"
# Failure, notify the user!
end
end
我建议如果获得作业已完成的通知很重要,那么编写一个自定义作业对象并排队,而不是依赖于在调用Available.delay.dosomething
时排队的默认作业。 创建一个像这样的对象:
class DoSomethingAvailableJob
attr_accessor options
def initialize(options = {})
@options = options
end
def perform
Available.dosomething(@options)
# Do some sort of notification here
# ...
end
end
并将其排列:
Delayed::Job.enqueue DoSomethingAvailableJob.new(:var => 1234)
应用程序中的delayed_jobs表旨在仅提供运行和排队作业的状态。 它不是一个持久表,并且出于性能原因,它应该尽可能小。 这就是为什么工作在完成后立即被删除。
相反,您应该在Available
模型中添加字段,表示作业已完成。 由于我通常对作业处理所需的时间感兴趣,因此我添加了start_time和end_time字段。 然后我的dosomething
方法看起来像这样:
def self.dosomething(model_id)
model = Model.find(model_id)
begin
model.start!
# do some long work ...
rescue Exception => e
# ...
ensure
model.finish!
end
end
开始! 并完成! 方法只记录当前时间并保存模型。 然后我会completed?
一个completed?
您的AJAX可以轮询以查看作业是否已完成的方法。
def completed?
return true if start_time and end_time
return false
end
有很多方法可以做到这一点,但我发现这种方法很简单,对我来说效果很好。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.