使用sidekiq处理两个单独的redis实例?

时间:2013-02-07 00:47:18

标签: ruby-on-rails redis queue sidekiq

下午好,

我有两个独立但相关的应用程序。他们应该都有自己的后台队列(阅读:单独的Sidekiq& Redis进程)。但是,我希望偶尔能够将作业从app2推送到app1的队列。

从简单的队列/推送角度来看,如果app1没有现有的Sidekiq / Redis堆栈,那么很容易做到这一点:

# In a process, far far away

# Configure client 
Sidekiq.configure_client do |config|
  config.redis = { :url => 'redis://redis.example.com:7372/12', :namespace => 'mynamespace' }
end

# Push jobs without class definition 
Sidekiq::Client.push('class' => 'Example::Workers::Trace', 'args' => ['hello!'])

# Push jobs overriding default's 
Sidekiq::Client.push('queue' => 'example', 'retry' => 3, 'class' =>     'Example::Workers::Trace', 'args' => ['hello!'])

但是,鉴于我已经从Sidekiq.configure_client调用了Sidekiq.configure_serverapp1,可能会在这里发生一些需要发生的事情。

显然,我可以直接从Sidekiq内部获取序列化和规范化代码,并手动推送到app2的redis队列,但这似乎是一个脆弱的解决方案。我希望能够使用Client.push功能。

我想我的理想解决方案就像:

SidekiqTWO.configure_client { remote connection..... } SidekiqTWO::Client.push(job....)

甚至:

$redis_remote = remote_connection.....

Sidekiq::Client.push(job, $redis_remote)

显然有点滑稽,但那是我理想的用例。

谢谢!

3 个答案:

答案 0 :(得分:8)

所以有一点是According to the FAQ," Sidekiq消息格式非常简单且稳定:它只是JSON格式的哈希。&#34 ;强调我的 - 我不认为向sidekiq发送JSON太脆弱了。特别是当你想要对你发送作业的Redis实例进行细粒度控制时,就像OP的情况一样,我可能只是写一个小包装器,让我指示一个Redis实例以及工作入队。

对于Kevin Bedell更常见的将工作轮换到Redis实例的情况,我想象你希望控制哪个Redis实例使用 - 您只想排队并自动管理分发。它看起来像only one person has requested this so far,而they came up with a solution使用Redis::Distributed

datastore_config = YAML.load(ERB.new(File.read(File.join(Rails.root, "config", "redis.yml"))).result)

datastore_config = datastore_config["defaults"].merge(datastore_config[::Rails.env])

if datastore_config[:host].is_a?(Array)
  if datastore_config[:host].length == 1
    datastore_config[:host] = datastore_config[:host].first
  else
    datastore_config = datastore_config[:host].map do |host|
      host_has_port = host =~ /:\d+\z/

      if host_has_port
        "redis://#{host}/#{datastore_config[:db] || 0}"
      else
        "redis://#{host}:#{datastore_config[:port] || 6379}/#{datastore_config[:db] || 0}"
      end
    end
  end
end

Sidekiq.configure_server do |config|
  config.redis = ::ConnectionPool.new(:size => Sidekiq.options[:concurrency] + 2, :timeout => 2) do
    redis = if datastore_config.is_a? Array
      Redis::Distributed.new(datastore_config)
    else
      Redis.new(datastore_config)
    end

    Redis::Namespace.new('resque', :redis => redis)
  end
end

在寻求高可用性和故障转移的过程中需要考虑的另一件事是获得Sidekiq Pro,其中包括可靠性功能:" Sidekiq Pro客户端可以承受瞬态Redis中断。它会在出错时在本地排队作业,并在连接恢复后尝试提供这些作业。"由于sidekiq无论如何都是用于后台进程,如果Redis实例关闭,短暂的延迟不应该影响您的应用程序。如果您的两个Redis实例中有一个发生故障并且您正在使用循环法,那么除非您正在使用此功能,否则您仍然会丢失一些作业。

答案 1 :(得分:2)

正如carols10cents所说的那么简单但是我总是希望封装这个功能并能够在其他项目中重用它,我从blog from Hotel Tonight更新了一个想法。以下解决方案改进了Hotel Tonight's并没有在Rails 4.1&弹簧预载器。

目前,我将以下文件添加到lib/remote_sidekiq/

<强> remote_sidekiq.rb

class RemoteSidekiq
  class_attribute :redis_pool
end

<强> remote_sidekiq_worker.rb

require 'sidekiq'
require 'sidekiq/client'

module RemoteSidekiqWorker
  def client
    pool = RemoteSidekiq.redis_pool || Thread.current[:sidekiq_via_pool] || Sidekiq.redis_pool
    Sidekiq::Client.new(pool)
  end

  def push(worker_name, attrs = [], queue_name = "default")
    client.push('args' => attrs, 'class' => worker_name, 'queue' => queue_name)
  end
end

您需要创建一个设置redis_pool

的初始化程序

<强>配置/初始化/ remote_sidekiq.rb

url = ENV.fetch("REDISCLOUD_URL")
namespace = 'primary'

redis = Redis::Namespace.new(namespace, redis: Redis.new(url: url))

RemoteSidekiq.redis_pool = ConnectionPool.new(size: ENV['MAX_THREADS'] || 6) { redis }

然后,您可以随时随地使用include RemoteSidekiqWorker模块。完成工作!

**** 更大的环境 ****

添加RemoteWorker模型可带来额外的好处:

  1. 您可以在任何地方重复使用RemoteWorkers,包括可以访问目标sidekiq worker的系统。这对来电者来说是透明的。使用&#34; RemoteWorkers&#34;目标sidekiq系统中的表单根本不使用初始化程序,因为它默认使用本地Sidekiq客户端。
  2. 使用RemoteWorkers确保始终发送正确的参数(代码=文档)
  3. 通过创建更复杂的Sidekiq架构进行扩展对于调用者来说是透明的。
  4. 以下是RemoteWorker的示例

    class RemoteTraceWorker
      include RemoteSidekiqWorker
      include ActiveModel::Model
    
      attr_accessor :message
    
      validates :message, presence: true
    
      def perform_async
        if valid?
          push(worker_name, worker_args)
        else
          raise ActiveModel::StrictValidationFailed, errors.full_messages
        end
      end
    
      private
    
      def worker_name
        :TraceWorker.to_s
      end
    
      def worker_args
        [message]
      end
    end
    

答案 2 :(得分:0)

我遇到了这个并遇到了一些问题因为我使用的是def hideMe(self): self.old_pos = self.pos() self.hide() QTimer.singleShot(300, self.showMe) def showMe(self): self.show() self.move(self.old_pos) ,这使得从队列中读取消息的方式变得复杂。

基于ARO的答案,您仍然需要redis_pool设置:

<强> remote_sidekiq.rb

ActiveJob

<强>配置/初始化/ remote_sidekiq.rb

class RemoteSidekiq
  class_attribute :redis_pool
end

现在我们将创建一个ActiveJob适配器来排队请求,而不是工作者:

<强> LIB / active_job / queue_adapters / remote_sidekiq_adapter.rb

url = ENV.fetch("REDISCLOUD_URL")
namespace = 'primary'

redis = Redis::Namespace.new(namespace, redis: Redis.new(url: url))

RemoteSidekiq.redis_pool = ConnectionPool.new(size: ENV['MAX_THREADS'] || 6) { redis }

我现在可以使用适配器对事件进行排队:

require 'sidekiq'

module ActiveJob
  module QueueAdapters
    class RemoteSidekiqAdapter
      def enqueue(job)
        #Sidekiq::Client does not support symbols as keys
        job.provider_job_id = client.push \
          "class"   => ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper,
          "wrapped" => job.class.to_s,
          "queue"   => job.queue_name,
          "args"    => [ job.serialize ]
      end

      def enqueue_at(job, timestamp)
        job.provider_job_id = client.push \
          "class"   => ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper,
          "wrapped" => job.class.to_s,
          "queue"   => job.queue_name,
          "args"    => [ job.serialize ],
          "at"      => timestamp
      end

      def client
        @client ||= ::Sidekiq::Client.new(RemoteSidekiq.redis_pool)
      end
    end
  end
end

我现在可以使用普通的ActiveJob api对作业进行排队。无论什么应用程序从队列中读取此内容,都需要有匹配的require 'active_job/queue_adapters/remote_sidekiq_adapter' class RemoteJob < ActiveJob::Base self.queue_adapter = :remote_sidekiq queue_as :default def perform(_event_name, _data) fail " This job should not run here; intended to hook into ActiveJob and run in another system " end end 来执行操作。