如何查找 ActiveRecord 模型对象的关联 Resque 作业



我需要能够找到模型对象的排队和/或工作作业和/或失败的作业,例如,当模型对象被销毁时,我们希望找到所有内容,并决定不删除或销毁作业(有条件)。

在我重新发明轮子之前,有没有推荐的方法可以做到这一点?

例:

如果要创建一个before_destroy回调,该回调在销毁对象时销毁所有作业(排队和失败的作业),并且仅在没有工作作业时才销毁

我正在考虑为此示例用例执行的一些伪代码:

报表模型

class Report < ActiveRecord::Base
  before_destroy :check_if_working_jobs, :destroy_queued_and_failed_jobs
  def check_if_working_jobs
    # find all working jobs related to this report object
    working_jobs = ProcessReportWorker.find_working_jobs_by_report_id(self.id) 
    return false unless working_jobs.empty?
  end
  def destroy_queued_and_failed_jobs
    # find all jobs related to this report object
    queued_jobs = ProcessReportWorker.find_queued_jobs_by_report_id(self.id) 
    failed_jobs = ProcessReportWorker.find_failed_jobs_by_report_id(self.id) 
    # destroy/remove all jobs found
    (queued_jobs + failed_jobs).each do |job| 
       # destroy the job here ... commands?
    end
  end
end

reque/redis 支持的作业的报告处理工作线程类

class ProcessReportWorker
  # find the jobs by report id which is one of the arguments for the job?
  # envisioned as separate methods so they can be used independently as needed
  def self.find_queued_jobs_by_report_id(id)
     # parse all jobs in all queues to find based on the report id argument?
  end 
  def self.find_working_jobs_by_report_id(id)
     # parse all jobs in working queues to find based on the report id argument? 
  end 
  def self.find_failed_jobs_by_report_id(id)
     # parse all jobs in failed queue to find based on the report id argument? 
  end 
end

这种方法是否与需要发生的事情保持一致?

上面缺少哪些部分来按模型对象 ID 查找排队或工作作业,然后将其销毁?

是否已经有适当的方法来查找和/或销毁我在文档或搜索中遗漏的关联模型对象 ID?

更新:修改了使用示例,仅使用 working_jobs 作为检查我们是否应该删除的一种方式,而不是建议我们也尝试删除working_jobs。(因为删除工作作业比简单地删除 Redis 密钥条目更复杂)

这里很安静,没有回应,所以我设法按照我在问题中指出的路径自己解决这个问题。可能有更好的解决方案或其他可用的方法,但这似乎可以完成到目前为止的工作。请随时评论此处是否有更好的方法选择,或者是否可以进一步改进。

这里的总体方法是,您需要搜索所有作业(排队、工作、失败),并仅过滤掉相关且与您在 args 数组的正确索引位置中查找的对象记录 ID 匹配的classqueue的作业。例如(确认classqueue匹配后),如果参数位置 0 是对象 id 所在的位置,则可以测试args[0]是否与对象 id 匹配。

实质上,在以下情况下,作业与对象 ID 相关联: job_class == class.name && job_queue == @queue && job_args[OBJECT_ID_ARGS_INDEX].to_i == object_id

    排队
  • 作业:要查找所有排队作业,您需要收集所有 redis具有名为 queue:#{@queue} 的键的条目,其中 @queue 是辅助角色类正在使用的队列的名称。通过循环遍历多个队列(如果使用多个队列)一个特定的工人阶级。 Resque.redis.lrange("queue:#{@queue}",0,-1)
  • 失败的作业
  • :要查找所有排队的作业,您需要收集所有 redis具有名为 failed 的键的条目(除非您使用多个失败队列或默认设置以外的其他内容)。 Resque.redis.lrange("failed",0,-1)
  • 工作
  • 工作:要查找您可以使用的所有工作工作Resque.workers它包含所有工作线程和正在运行的作业的数组。 Resque.workers.map(&:job)
  • 作业
  • 上述每个列表中的每个作业都将是一个编码的哈希。你可以使用 Resque.decode(job) 将作业解码为 Ruby 哈希。
  • 类和参数:对于排队的作业class键和argsjob["class"]job["args"].对于失败工作的工作,这些是job["payload"]["class"]job["payload"]["args"]
  • 队列
  • 对于找到的每个失败工作作业,队列将job["queue"]。在测试对象 ID 的参数列表之前,您只需要与 classqueue 匹配的作业。排队的作业列表已限制为您收集的队列。
下面是用于查找(和

删除)与示例模型对象(报表)关联的作业的示例辅助角色类和模型方法。

reque/redis 支持的作业的报告处理工作线程类

class ProcessReportWorker
  # queue name
  @queue = :report_processing
  # tell the worker class where the report id is in the arguments list
  REPORT_ID_ARGS_INDEX = 0 
  # <snip> rest of class, not needed here for this answer
  # find jobs methods - find by report id (report is the 'associated' object)
  def self.find_queued_jobs_by_report_id report_id
    queued_jobs(@queue).select do |job|
      is_job_for_report? :queued, job, report_id
    end
  end
  def self.find_failed_jobs_by_report_id report_id
    failed_jobs.select do |job|
      is_job_for_report? :failed, job, report_id
    end
  end
  def self.find_working_jobs_by_report_id report_id
    working_jobs.select do |worker,job|
      is_job_for_report? :working, job, report_id
    end
  end
  # association test method - determine if this job is associated      
  def self.is_job_for_report? state, job, report_id
    attributes = job_attributes(state, job)
    attributes[:klass] == self.name && 
      attributes[:queue] == @queue && 
        attributes[:args][REPORT_ID_ARGS_INDEX].to_i == report_id
  end
  # remove jobs methods
  def self.remove_failed_jobs_by_report_id report_id
    find_failed_jobs_by_report_id(report_id).each do |job|
      Resque::Failure.remove(job["index"]) 
    end
  end
  def self.remove_queued_jobs_by_report_id report_id
    find_queued_jobs_by_report_id(report_id).each do |job|
      Resque::Job.destroy(@queue,job["class"],*job["args"])
    end
  end
  # reusable methods - these methods could go elsewhere and be reusable across worker classes
  # job attributes method
  def self.job_attributes(state, job)
    if state == :queued && job["args"].present?
      args = job["args"]
      klass = job["class"]
    elsif job["payload"] && job["payload"]["args"].present?
      args = job["payload"]["args"]
      klass = job["payload"]["class"] 
    else
      return {args: nil, klass: nil, queue: nil}
    end
    {args: args, klass: klass, queue: job["queue"]}
  end
  # jobs list methods
  def self.queued_jobs queue
    Resque.redis.lrange("queue:#{queue}", 0, -1)
      .collect do |job| 
        job = Resque.decode(job)
        job["queue"] = queue # for consistency only
        job
      end
  end
  def self.failed_jobs
    Resque.redis.lrange("failed", 0, -1)
      .each_with_index.collect do |job,index|
        job = Resque.decode(job)
        job["index"] = index # required if removing
        job
      end
  end
  def self.working_jobs
    Resque.workers.zip(Resque.workers.map(&:job))
      .reject { |w, j| w.idle? || j['queue'].nil? }
  end
end

因此,报表模型的使用示例变为

class Report < ActiveRecord::Base
  before_destroy :check_if_working_jobs, :remove_queued_and_failed_jobs
  def check_if_working_jobs
    # find all working jobs related to this report object
    working_jobs = ProcessReportWorker.find_working_jobs_by_report_id(self.id) 
    return false unless working_jobs.empty?
  end
  def remove_queued_and_failed_jobs
    # find all jobs related to this report object
    queued_jobs = ProcessReportWorker.find_queued_jobs_by_report_id(self.id) 
    failed_jobs = ProcessReportWorker.find_failed_jobs_by_report_id(self.id) 
    # extra code and conditionals here for example only as all that is really 
    # needed is to call the remove methods without first finding or checking
    unless queued_jobs.empty?
      ProcessReportWorker.remove_queued_jobs_by_report_id(self.id)
    end
    unless failed_jobs.empty?
      ProcessReportWorker.remove_failed_jobs_by_report_id(self.id)
    end
  end
end

如果对辅助角色类使用多个队列,或者有多个故障队列,则需要修改解决方案。此外,还使用了redis故障后端。如果使用其他故障后端,则可能需要进行更改。

最新更新