下午好,
我有两个独立但相关的应用程序。他们应该都有自己的后台队列(阅读:单独的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_server
和app1
,可能会在这里发生一些需要发生的事情。
显然,我可以直接从Sidekiq内部获取序列化和规范化代码,并手动推送到app2
的redis队列,但这似乎是一个脆弱的解决方案。我希望能够使用Client.push
功能。
我想我的理想解决方案就像:
SidekiqTWO.configure_client { remote connection..... }
SidekiqTWO::Client.push(job....)
甚至:
$redis_remote = remote_connection.....
Sidekiq::Client.push(job, $redis_remote)
显然有点滑稽,但那是我理想的用例。
谢谢!
答案 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模型可带来额外的好处:
以下是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
来执行操作。