Работа с двумя отдельными экземплярами redis с sidekiq?

Добрый день,

у меня есть два отдельных, но связанных между собой приложений. Они оба должны иметь свои собственные фоновые очереди (читать: отдельные процессы Sidekiq & Redis). Тем не менее, я хотел бы иногда иметь возможность нажимать задания на С 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_server С app1, вероятно, есть шаг между здесь, где что-то должно произойти.

очевидно, я мог бы просто взять код сериализации и нормализации прямо изнутри Sidekiq и вручную нажать на app2очередь redis, но это кажется хрупким решением. Я хотел бы иметь возможность использовать Client.push функциональность.

Я полагаю, что мое идеальное решение было бы someting например:

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

или еще:

$redis_remote = remote_connection.....

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

очевидно, немного шутливо,но это мой идеальный вариант использования.

спасибо!

3 ответов


Итак, одно дело, что согласно FAQ, "формат сообщения Sidekiq довольно прост и стабильный: это просто Хэш в формате JSON.- Подчеркиваю-я не думаю, что посылать ДЖСОНА в сайдкик слишком сложно. Особенно, когда вам нужен мелкозернистый контроль, вокруг которого экземпляр Redis вы отправляете задания, как в ситуации OP, я, вероятно, просто напишу небольшую обертку, которая позволит мне указать экземпляр Redis вместе с заданием поставить в очередь.

для более общей ситуации Кевина Беделла для круговых заданий В экземпляры Redis, я бы представил вас не хотите иметь контроль над тем, какой экземпляр Redis используется - вы просто хотите запросить и управлять распределением автоматически. Похоже на только один человек запросил это до сих пор и они придумали решение использует 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 отключается, и вы используете round robin, вы все еще потеряли некоторые рабочие места, если вы не используете эту функцию.


как carols10cents говорит, что это довольно просто, но, как я всегда хотел инкапсулировать возможность и иметь возможность повторно использовать его в других проектах, я обновил идею из блог из отеля Сегодня вечером. Это следующее решение улучшает отель Сегодня вечером, который не переживает Rails 4.1 & Spring preloader.

в настоящее время я делаю с добавлением следующих файлов в 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

config/инициализаторы / 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. Это прозрачно для вызывающего абонента. Для использования формы "RemoteWorkers" в целевой системе sidekiq просто не используйте инициализатор, поскольку по умолчанию используется локальный клиент Sidekiq.
  2. использование RemoteWorkers гарантирует, что правильные аргументы всегда отправляются (код = документация)
  3. масштабирование по создание более сложных архитектур Sidekiq прозрачно для абонента.

вот пример 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

я наткнулся на это и столкнулся с некоторыми проблемами, потому что я использую ActiveJob, что усложняет чтение сообщений из очереди.

основываясь на ответе АРО, вам все равно понадобится настройка redis_pool:

remote_sidekiq.rb

class RemoteSidekiq
  class_attribute :redis_pool
end

config/инициализаторы / 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 }

теперь вместо рабочего мы создадим адаптер ActiveJob для очереди запрос:

lib/active_job/queue_adapters / remote_sidekiq_adapter.rb

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

Я могу использовать адаптер для очереди событий сейчас:

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

теперь я могу поставить задание в очередь с помощью обычного api ActiveJob. Независимо от того, приложение читает это из очереди, нужно будет иметь соответствующий RemoteJob для выполнения действий.