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