sidekiq - 当redis服务器未运行时,回退到标准同步ruby代码

时间:2013-04-13 21:20:03

标签: ruby-on-rails redis sidekiq

我在rails应用中使用sidekiq以异步方式发送一些电子邮件。即使Redis服务器未运行,我如何确保执行代码(作业本身)。

CommentsWorker.perform_async(@user.id, @comment.id)

在评论工作者中,我正在提取用户和评论,然后发送电子邮件:

def perform(user_id, comment_id)
  user = User.find(user_id)
  comment = Comment.find(comment_id)

  CommentMailer.new_comment(user, comment).deliver
end

如果我停止Redis服务器,我的应用程序会引发异常Redis::CannotConnectError

即使服务器停止,我仍然希望使用旧式同步代码发送该电子邮件。我尝试从该异常rescue,但由于某种原因它不起作用。

6 个答案:

答案 0 :(得分:5)

想出来。解决方案是测试redis连接并从异常中解救,但之前调用perform_async。现在只有一个小问题就是必须等待连接超时,但我想我可以忍受这个。

redis_available = true
Sidekiq.redis do |connection|
  begin
    connection.info
  rescue Redis::CannotConnectError
    redis_available = false
  end
end


if redis_available
  CommentsWorker.perform_async(user.id, @comment.id, @award.id)
else
   #sync code
   user = User.find(user_id)
   comment = Comment.find(comment_id)

   CommentMailer.new_comment(user, comment).deliver
end

答案 1 :(得分:2)

I know this has already been answered and I liked @mihai's approach, but we wanted to reuse the code elsewhere in our application for multiple workers. Also, we wanted to make it more generic to work with any worker.

We decided to extend Sidekiq::Worker with an additional method perform_async_with_failover defined below:

module Sidekiq
  module Worker
    module ClassMethods
      def perform_async_with_failover(*args)
        redis_available = true
        Sidekiq.redis do |connection|
          begin
            connection.info
          rescue Redis::CannotConnectError
            redis_available = false
          end
        end
        if redis_available
          # process the job asynchronously
          perform_async(*args)
        else
          # otherwise, instantiate and perform synchronously
          self.new.perform(*args)
        end
      end
    end
  end
end

答案 2 :(得分:1)

改进Kyle的解决方案,因为我想使用connection.info是为了查看是否有可用的连接。但我的理解是,info会在redis联机时发送额外的命令,这是不必要的。我只想从perform_async获取异常:

module Sidekiq
  module Worker
    module ClassMethods
      def perform_async_with_failover(*args)
        begin
          # process the job asynchronously
          perform_async(*args)
        rescue Redis::CannotConnectError => e
          # otherwise, instantiate and perform synchronously
          self.new.perform(*args)
        end
      end
    end
  end
end

答案 3 :(得分:1)

实现@mihai的答案,我最终创建了一个服务来封装“优雅地发送电子邮件”的动作。

调用类的示例:

message = MyMailer.order_confirmation(email_arguments)
GracefullyDeliverEmail.call(message)

课程:

class GracefullyDeliverEmail
  ###
  # Attempts to queue email for async sending but fails
  # gracefully to delivering it immediately.
  #
  # @param context.message {ActionMailer::MessageDelivery}
  ###
  def self.call(message)
    validate!(message)

    if redis_available?
      message.deliver_later(wait: 2.mins)
    else
      message.deliver_now # Fallback to inline delivery
    end
  end

  # == Private Methods ======================================================
  private

  # https://stackoverflow.com/questions/15993080/sidekiq-fall-back-to-standard-sync-ruby-code-when-redis-server-is-not-running/42247913#42247913
  def redis_available?
    redis_available = true
    Sidekiq.redis do |connection|
      begin
        connection.info
      rescue Redis::CannotConnectError
        redis_available = false
      end
    end
    redis_available
  end

  def validate!(message)
    if !message.is_a?(ActionMailer::MessageDelivery)
      raise "message must be of class ActionMailer::MessageDelivery"
    end
  end
end

答案 4 :(得分:0)

<强>编辑
你可能想看看sidekiq error handling section

答案 5 :(得分:0)

您应该检查是否存在活动的redis客户端,例如:

def perform(user_id, comment_id)
  user = User.find(user_id)
  comment = Comment.find(comment_id)

  redis_info = Sidekiq.redis { |conn| conn.info }

  CommentMailer.new_comment(user, comment).deliver

  rescue Redis::CannotConnectError
    CommentMailer.new_comment(user, comment)
end

应该这样做。

相关问题