【问题标题】:How can I make sure that the same delayed job does not twice at the same time (mutually exclusive)如何确保同一个延迟作业不会同时出现两次(互斥)
【发布时间】:2025-12-02 03:30:02
【问题描述】:

对于一个项目,我想确保某个作业不会同时运行两次。这项工作是一个进口商,如果它仍在运行,再次运行是没有意义的。如果我们检测到作业已经在运行,我想引发一个异常,以便我收到有关长时间运行的作业的警报。

【问题讨论】:

  • 使用可能只有一个工作人员的单独队列?
  • 是的,我考虑过这个,但我们有大约 50 个不同的工作 :) 我认为这会有点失控 :) 谢谢你的建议!

标签: ruby-on-rails ruby delayed-job


【解决方案1】:

我找到了解决这个问题的方法。我们介绍了一个新的custom job。当作业执行时,我们检查是否已经有另一个作业在运行:

# checks if the job is already running when it is supposed to be performed.
# if it is already running, then we raise an error
# jobs with different arguments but same object and method raise an error.
class ExclusiveJob < Struct.new(:object, :meth, :args)
  class ExclusiveJobError < RuntimeError; end

  def initialize(object, meth, *args)
    super(object, meth, args)
  end

  def perform
    same_and_current_jobs = Delayed::Job
      .where(last_error: nil)
      .where.not(locked_at: nil)
      .collect { |job| YAML.load(job.handler) }
      .select { |handler| handler.is_a?(self.class) && handler.object == object && handler.meth == meth }
    
    raise ExclusiveJobError, "Tried to perform \"#{identifier}\", but it is already running" if same_and_current_jobs.count > 1 # we have more than this job in the pipeline

    object.send(meth, *args)
  end

  protected

  # just for display purposes
  def identifier
    "#{object}##{meth}"
  end
end

请注意,这段代码并不理想,因为我们依赖于延迟作业的内部数据模型(但它是officially documented)。 另外,请注意,此类不考虑方法参数,即如果找到具有相同接收器和方法的另一个作业,我们将跳过该作业。 而且,我们没有使用 ActiveJob,可能有一些方法可以使用 callbacks 解决这个问题。

要将这样一个排他性的工作排入队列:

Delayed::Job.enqueue ExclusiveJob.new(MySuperService, :run, 'arg1', :arg2)

【讨论】:

    最近更新 更多