3

我在 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摆脱那个异常,但由于某种原因它不起作用。

4

6 回答 6

7

弄清楚了。解决方案是测试 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
于 2013-04-13T22:22:47.360 回答
4

我知道这已经得到解答,我喜欢@mihai 的方法,但我们希望在我们的应用程序的其他地方为多个工作人员重用代码。此外,我们想让它更通用地与任何工人一起工作。

我们决定使用下面定义Sidekiq::Worker的附加方法进行扩展perform_async_with_failover

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
于 2016-08-31T16:49:31.337 回答
2

改进上面的凯尔解决方案,因为我猜想使用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
于 2017-02-15T11:21:07.453 回答
1

编辑
你可能想看看sidekiq 错误处理部分

于 2013-04-13T21:27:16.877 回答
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
于 2018-11-09T23:52:01.393 回答
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

应该这样做。

于 2013-04-13T21:52:56.313 回答