我有一个应用程序可以响应客户端发送的消息。一条消息是reload_credentials
,应用程序会在新客户端注册时收到该消息。然后,此消息将连接到PostgreSQL数据库,对所有凭据执行查询,然后将它们存储在常规Ruby散列(client_id => client_token)中。
应用程序可能会收到的其他一些消息是start
,stop
,pause
,用于跟踪某些会话时间。我的观点是,我设想应用程序以下列方式运行:
但是,例如,我不想阻止反应堆。此外,让我们想象一下,我的队列中有一条reload_credentials
消息。在从数据库重新加载凭据之前,我不希望处理队列中的任何其他消息。此外,当我处理某个消息(比如等待凭据查询完成)时,我想允许其他消息入队。
你能指导我解决这个问题吗?我想我可能不得不使用em-synchrony
,但我不确定。
答案 0 :(得分:7)
使用其中一个Postgresql EM驱动程序或EM.defer,这样就不会阻塞反应堆。
当您收到'reload_credentials'消息时,只需翻转一个标志,该标志会导致所有后续消息入队。 'reload_credentials'完成后,处理队列中的所有消息。在队列为空之后,翻转导致消息在接收时被处理的标志。
Postgresql的EM驱动程序列于此处:https://github.com/eventmachine/eventmachine/wiki/Protocol-Implementations
module Server
def post_init
@queue = []
@loading_credentials = false
end
def recieve_message(type, data)
return @queue << [type, data] if @loading_credentials || !@queue.empty?
return process_msg(type, data) unless :reload_credentials == type
@loading_credentials = true
reload_credentials do
@loading_credentials = false
process_queue
end
end
def reload_credentials(&when_done)
EM.defer( proc { query_and_load_credentials }, when_done )
end
def process_queue
while (type, data = @queue.shift)
process_msg(type, data)
end
end
# lots of other methods
end
EM.start_server(HOST, PORT, Server)
如果您希望所有连接在任何连接收到“reload_connections”消息时对消息进行排队,则必须通过本征类进行协调。
答案 1 :(得分:4)
以下是我的假设,类似于您当前的实现:
class Worker
def initialize queue
@queue = queue
dequeue
end
def dequeue
@queue.pop do |item|
begin
work_on item
ensure
dequeue
end
end
end
def work_on item
case item.type
when :reload_credentials
# magic happens here
else
# more magic happens here
end
end
end
q = EM::Queue.new
workers = Array.new(10) { Worker.new q }
如果我理解正确的话,上面的问题是,您不希望工作人员处理新作业(生产者时间轴中较早到达的作业),而不是任何reload_credentials作业。以下内容应该为此提供服务(最后还要提醒注意)。
class Worker
def initialize queue
@queue = queue
dequeue
end
def dequeue
@queue.pop do |item|
begin
work_on item
ensure
dequeue
end
end
end
def work_on item
case item.type
when :reload_credentials
# magic happens here
else
# more magic happens here
end
end
end
class LockingDispatcher
def initialize channel, queue
@channel = channel
@queue = queue
@backlog = []
@channel.subscribe method(:dispatch_with_locking)
@locked = false
end
def dispatch_with_locking item
if locked?
@backlog << item
else
# You probably want to move the specialization here out into a method or
# block that's passed into the constructor, to make the lockingdispatcher
# more of a generic processor
case item.type
when :reload_credentials
lock
deferrable = CredentialReloader.new(item).start
deferrable.callback { unlock }
deferrable.errback { unlock }
else
dispatch_without_locking item
end
end
end
def dispatch_without_locking item
@queue << item
end
def locked?
@locked
end
def lock
@locked = true
end
def unlock
@locked = false
bl = @backlog.dup
@backlog.clear
bl.each { |item| dispatch_with_locking item }
end
end
channel = EM::Channel.new
queue = EM::Queue.new
dispatcher = LockingDispatcher.new channel, queue
workers = Array.new(10) { Worker.new queue }
因此,第一个系统的输入会出现在q
上,但在这个新系统中,它会进入channel
。 queue
仍用于工作人员之间的工作分配,但在刷新凭据操作正在进行时未填充queue
。遗憾的是,由于我没有花费更多时间,因此我没有概括LockingDispatcher
,因此它没有与调度CredentialsReloader
的项类型和代码相结合。我会留给你的。
您应该在此注意,虽然这可以解释我对您原始请求的理解,但通常最好放宽这种要求。如果不改变该要求,有几个突出的问题基本上无法根除:
实际上听起来你在系统中有一些用户ID的概念。如果您仔细考虑您的要求,则可能只需要对与凭据处于刷新状态的用户标识相关的项目进行积压。这是一个不同的问题,涉及不同类型的调度。尝试为这些用户提供锁定积压的哈希值,并在凭证完成时回调以将这些积压消耗到工作人员或类似的安排中。
祝你好运!