订阅队列,接收1条消息,然后取消订阅

时间:2011-04-15 23:13:30

标签: ruby rabbitmq amqp

我有一个场景,我需要非常快速地分发和处理作业。我将在队列中快速填充大约45个作业,我可以同时处理大约20个作业(5台机器,每台4个核心)。每个作业都需要花费不同的时间,而且复杂的事情是垃圾收集是一个问题所以我需要能够让消费者离线进行垃圾收集。

目前,我有一切都在使用pop(每个消费者每5分钟就会弹出一次)。这似乎是不合需要的,因为它转换为每秒600次popq请求到rabbitmq。

我很乐意,如果有一个pop命令会像订阅一样,但只有一条消息。 (进程会阻塞,等待来自rabbitMQ连接的输入,通过类似于Kernel.select的东西)

我试图欺骗AMQP gem来做这样的事情,但是它不起作用:在队列为空并且不再向消费者发送消息之前,我似乎无法取消订阅。其他取消订阅的方法我担心会失去信息。

consume_1.rb:

require "amqp"

EventMachine.run do
  puts "connecting..."
  connection = AMQP.connect(:host => "localhost", :user => "guest", :pass => "guest", :vhost => "/")
  puts "Connected to AMQP broker"

  channel = AMQP::Channel.new(connection)
  queue = channel.queue("tasks", :auto_delete => true)
  exchange = AMQP::Exchange.default(channel)

  queue.subscribe do |payload|
    puts "Received a message: #{payload}."
    queue.unsubscribe { puts "unbound" }
    sleep 3
  end
end

consumer_many.rb:

require "amqp"

# Imagine the command is something CPU - intensive like image processing.
command = "sleep 0.1"

EventMachine.run do
  puts "connecting..."
  connection = AMQP.connect(:host => "localhost", :user => "guest", :pass => "guest", :vhost => "/")
  puts "Connected to AMQP broker"

  channel = AMQP::Channel.new(connection)
  queue = channel.queue("tasks", :auto_delete => true)
  exchange = AMQP::Exchange.default(channel)

  queue.subscribe do |payload|
    puts "Received a message: #{payload}."
  end
end

producer.rb:

require "amqp"

i = 0
EventMachine.run do
  connection = AMQP.connect(:host => "localhost", :user => "guest", :pass => "guest", :vhost => "/")
  puts "Connected to AMQP broker"

  channel = AMQP::Channel.new(connection)
  queue = channel.queue("tasks", :auto_delete => true)
  exchange = AMQP::Exchange.default(channel)

  EM.add_periodic_timer(1) do
    msg = "Message #{i}"
    i+=1
    puts "~ publishing #{msg}"
  end
end

我将启动consume_many.rb和producer.rb。消息将按预期流动。

当我启动consume_1.rb时,它会获取所有其他消息(如预期的那样)。但它永远不会取消订阅,因为它永远不会完成所有消息的处理......所以就这样了。

如何让consume_1.rb订阅队列,获取单个消息,然后将自己从负载均衡器环中取出,以便它可以正常工作,而不会丢失可能在其中的任何其他待处理作业队列,否则将安排发送到进程?

2 个答案:

答案 0 :(得分:13)

这是AMQP宝石的一个简单但很难记录的功能,你需要的是:

在您的消费者中:

channel = AMQP::Channel.new(connection, :prefetch => 1)

然后使用您的订阅块,执行:

queue.subscribe(:ack => true) do |queue_header, payload|
  puts "Received a message: #{payload}."
  # Do long running work here

  # Acknowledge message
  queue_header.ack
end

这是什么告诉RabbitMQ一次只发送消费者1消息,而不是在长时间运行后在队列头上调用ack之后再发送另一条消息任务一段时间。

我可能需要对此进行更正,但我相信direct交换更适合此任务。

答案 1 :(得分:1)

凭借我拥有的环境,

RabbitMQ版本:3.3.3

amqp gem version:1.5.0

Ivan的解决方案仍然导致从队列中提取所有消息。

相反,可以通过设置频道的QoS来限制订阅队列的未确认消息的数量。

根据AMQP :: Channel的API文档,

#qos(prefetch_size = 0, prefetch_count = 32, global = false, &block) ⇒ Object

该方法的一个注意事项是,如果您在版本2.3.6之后运行RabbitMQ服务器,则不推荐使用 prefetch_size

channel = AMQP::Channel.new(connection, :prefetch => 1)
channel.qos(0, 1)

queue = channel.queue(queue_name, :auto_delete => false)
queue.subscribe(:ack => true) do |metadata, payload|
  puts "Received a message: #{payload}."

  # Do long running work here

  # Acknowledge message
  metadata.ack
end

希望解决方案帮助某人。

干杯。