如何最好地保持作业队列清理重试/重复作业(使用sidekiq和redis-semaphore)

时间:2013-05-04 23:18:06

标签: ruby redis mutex semaphore sidekiq

我有一个rails应用程序可以从多个IMAP帐户中获取大量电子邮件。

  • 我使用 sidekiq 来处理这些工作。
  • 我使用 sidetiq 来安排工作。
  • 我使用 redis-semaphore 来确保同一用户的定期作业不会相互偶然发生。
然而,

2个问题:

  • 1:当作业点击“if s.lock” redis-semaphore 将其暂停,直到之前的所有作业都完成为止。我需要取消工作而不是排队。
  • 2:如果在作业期间出现异常,导致崩溃, sidekiq 会将作业重新放入队列进行重试。我需要取消工作而不是排队。将“sidekiq_options:retry => false”放入代码中似乎没什么区别。

我的代码:

class FetchMailsJobs
  include Sidekiq::Worker
  include Sidetiq::Schedulable

  tiq { hourly.minute_of_hour(0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55) }

  def perform(last_occurrence, current_occurrence)
    users = User.all
    users.each do |user|

      if user.imap_accounts.exists?
        ImapJob.perform_async(user._id.to_s)
      end
    end
  end
end

class ImapJob
  include Sidekiq::Worker

  def perform(user_id)
    s = Redis::Semaphore.new("fetch_imap_mails_for_#{user_id}".to_sym, connection: "localhost")
    if s.lock
      user = User.where(_id: user_id).first
      emails = ImapMails.receive_mails(user)
      s.unlock
    end
  end
end

2 个答案:

答案 0 :(得分:6)

1。创建Redis子类并重载blpop以接受-1,以便非阻塞地使用lpop

redis-semaphore Redis::Semaphore#lock中调用@redis.blpop。虽然您可以重载lock方法以使用@redis.lpop,但更简单的方法是将Redis的自定义实例传递给信号量。

将以下内容放入您的rails应用的lib中,并在config/initializers/sidekiq.rb中提出要求(或根据您的偏好执行以下操作,以便加载以下课程。)

class NonBlockingRedis < Redis
  def blpop(key, timeout)
    if timeout == -1
      result = lpop(key)
      return result if result.nil?
      return [key, result]
    else
      super(key, timeout)
    end
  end
end

每当您致电Redis::Semaphore.new时,请使用:redis类的新实例传递NonBlockingRedis密钥。

使用s.lock作为参数调用-1,以使用lpop代替blpop

s = Redis::Semaphore.new("fetch_imap_mails_for_#{user_id}".to_sym, redis: NonBlockingRedis.new(connection: "localhost"))
if s.lock -1
  user = User.where(_id: user_id).first
  emails = ImapMails.receive_mails(user)
  s.unlock
end

2。在您的worker类中使用sidekiq_options retry: false应该可以工作,请参阅下面的示例。

在您的问题中,您没有指定哪个工作人员遇到了在重试队列中结束的作业的问题。由于FetchMailsJobs结束了ImapJobImapJob个作业,因此前者中的例外情况可能会导致begin rescue ensure重新排队。

使用您的信号量锁定,将您的工作包装在class FetchMailsJobs include Sidekiq::Worker include Sidetiq::Schedulable sidekiq_options retry: false tiq { hourly.minute_of_hour(0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55) } def perform(last_occurrence, current_occurrence) users = User.all users.each do |user| if user.imap_accounts.exists? ImapJob.perform_async(user._id.to_s) end end end end class ImapJob include Sidekiq::Worker sidekiq_options retry: false def perform(user_id) s = Redis::Semaphore.new("fetch_imap_mails_for_#{user_id}".to_sym, redis: NonBlockingRedis.new(connection: "localhost")) if s.lock - 1 begin user = User.where(_id: user_id).first emails = ImapMails.receive_mails(user) rescue => e # ignore; do nothing ensure s.unlock end end end end 块中也是一个好主意。

{{1}}

有关详细信息,请参阅sidekiq Advanced Options: workers

答案 1 :(得分:1)

使用 redis-semaphore 并使用sidekiq-unique-jobs gem是不可能的?它似乎是一个很好的包含工具,可以完全满足您的需求。