RabbitMQ / AMQP:单个队列,同一个消息的多个消费者?

时间:2012-05-16 14:43:08

标签: node.js messaging rabbitmq amqp node-amqp

我刚刚开始使用RabbitMQ和AMQP。

  • 我有一个消息队列
  • 我有多个消费者,我想用相同的消息做不同的事情。

大多数RabbitMQ文档似乎都专注于循环,即单个消费者使用单个消息,负载在每个消费者之间传播。这确实是我见证的行为。

示例:生产者有一个队列,每隔2秒发送一次消息:

var amqp = require('amqp');
var connection = amqp.createConnection({ host: "localhost", port: 5672 });
var count = 1;

connection.on('ready', function () {
  var sendMessage = function(connection, queue_name, payload) {
    var encoded_payload = JSON.stringify(payload);  
    connection.publish(queue_name, encoded_payload);
  }

  setInterval( function() {    
    var test_message = 'TEST '+count
    sendMessage(connection, "my_queue_name", test_message)  
    count += 1;
  }, 2000) 


})

这是消费者:

var amqp = require('amqp');
var connection = amqp.createConnection({ host: "localhost", port: 5672 });
connection.on('ready', function () {
  connection.queue("my_queue_name", function(queue){
    queue.bind('#'); 
    queue.subscribe(function (message) {
      var encoded_payload = unescape(message.data)
      var payload = JSON.parse(encoded_payload)
      console.log('Recieved a message:')
      console.log(payload)
    })
  })
})

如果我启动消费者两次,我可以看到每个消费者在循环行为中消耗备用消息。例如,我会在一个终端中看到消息1,3,5,在另一个终端中看到2,4,6,

我的问题是:

  • 我可以让每个消费者收到相同的消息吗?即,两个消费者都得到消息1,2,3,4,5,6?在AMQP / RabbitMQ中这叫什么?通常如何配置?

  • 这是常见的吗?我应该将交换路由信息分成两个单独的队列,而不是一个消费者吗?

12 个答案:

答案 0 :(得分:90)

我可以让每个消费者收到相同的消息吗?即,两个消费者都得到消息1,2,3,4,5,6?在AMQP / RabbitMQ中这叫什么?通常如何配置?

不,如果消费者在同一个队列中则不行。来自RabbitMQ的AMQP Concepts指南:

  

重要的是要理解,在AMQP 0-9-1中,消息在消费者之间进行负载平衡。

这似乎意味着队列中的循环行为是给定的,并且不可配置。即,需要单独的队列才能使多个消费者处理相同的消息ID。

通常这样做吗?我应该让交换路由将消息分成两个单独的队列,而不是一个消费者吗?

不,不是,单个队列/多个消费者,每个消费者处理相同的消息ID是不可能的。将消息转换为两个单独的队列的交换路由确实更好。

由于我不需要太复杂的路由,扇出交换将很好地处理这个问题。我之前没有过多地关注Exchange,因为node-amqp具有“默认交换”的概念,允许您直接向连接发布消息,但是大多数AMQP消息都发布到特定的交换。

这是我的粉丝交换,包括发送和接收:

var amqp = require('amqp');
var connection = amqp.createConnection({ host: "localhost", port: 5672 });
var count = 1;

connection.on('ready', function () {
  connection.exchange("my_exchange", options={type:'fanout'}, function(exchange) {   

    var sendMessage = function(exchange, payload) {
      console.log('about to publish')
      var encoded_payload = JSON.stringify(payload);
      exchange.publish('', encoded_payload, {})
    }

    // Recieve messages
    connection.queue("my_queue_name", function(queue){
      console.log('Created queue')
      queue.bind(exchange, ''); 
      queue.subscribe(function (message) {
        console.log('subscribed to queue')
        var encoded_payload = unescape(message.data)
        var payload = JSON.parse(encoded_payload)
        console.log('Recieved a message:')
        console.log(payload)
      })
    })

    setInterval( function() {    
      var test_message = 'TEST '+count
      sendMessage(exchange, test_message)  
      count += 1;
    }, 2000) 
 })
})

答案 1 :(得分:17)

请阅读rabbitmq tutorial。您发布消息以进行交换,而不是排队;然后将其路由到适当的队列。在您的情况下,您应该为每个使用者绑定单独的队列。这样,他们可以完全独立地使用消息。

答案 2 :(得分:13)

最后几个答案几乎是正确的 - 我有大量的应用程序生成需要最终与不同消费者的消息,因此过程非常简单。

如果您希望多个消费者收到同一条消息,请执行以下步骤。

为每个要接收消息的应用创建多个队列,在每个队列属性中,使用amq.direct交换“绑定”路由标记。更改发布应用程序以发送到amq.direct并使用路由标记(不是队列)。然后,AMQP将使用相同的绑定将消息复制到每个队列中。作品谎言魅力:)

示例:假设我有一个我生成的JSON字符串,我使用路由标记“new-sales-order”将其发布到“amq.direct”交换,我有一个用于打印订单的order_printer应用程序的队列,我有一个我的计费系统的队列,它将发送订单的副本并为客户开具发票,我有一个网络存档系统,我存档订单是出于历史/合规性原因而且我有一个客户端网络界面,其中订单被跟踪为其他信息关于订单。

所以我的队列是:order_printer,order_billing,order_archive和order_tracking 所有绑定标签都绑定了“new-sales-order”,所有4个都将获得JSON数据。

这是一种理想的方式,可以在发布应用不知道或关心接收应用的情况下发送数据。

答案 3 :(得分:6)

发送模式是一对一的关系。如果您想“发送”到多个接收器,您应该使用发布/订阅模式。有关详细信息,请参阅http://www.rabbitmq.com/tutorials/tutorial-three-python.html

答案 4 :(得分:5)

是的,每个消费者都可以收到相同的消息。看一下 http://www.rabbitmq.com/tutorials/tutorial-three-python.html http://www.rabbitmq.com/tutorials/tutorial-four-python.html http://www.rabbitmq.com/tutorials/tutorial-five-python.html

用于路由邮件的不同方式。我知道它们适用于python和java,但它很好理解原理,决定你在做什么,然后找到如何在JS中做到这一点。听起来你想做一个简单的扇出(tutorial 3),它将消息发送到连接到交换机的所有队列。

与您正在做的事情和您想要做的事情的不同之处主要在于您要设置和交换或输入扇出。 Fanout excahnges将所有消息发送到所有连接的队列。每个队列都有一个消费者,可以分别访问所有消息。

是的,这是常见的,它是AMPQ的功能之一。

答案 5 :(得分:3)

RabbitMQ / AMQP:单个队列,多个消费者,用于相同的消息和页面刷新。

rabbit.on('ready', function () {    });
    sockjs_chat.on('connection', function (conn) {

        conn.on('data', function (message) {
            try {
                var obj = JSON.parse(message.replace(/\r/g, '').replace(/\n/g, ''));

                if (obj.header == "register") {

                    // Connect to RabbitMQ
                    try {
                        conn.exchange = rabbit.exchange(exchange, { type: 'topic',
                            autoDelete: false,
                            durable: false,
                            exclusive: false,
                            confirm: true
                        });

                        conn.q = rabbit.queue('my-queue-'+obj.agentID, {
                            durable: false,
                            autoDelete: false,
                            exclusive: false
                        }, function () {
                            conn.channel = 'my-queue-'+obj.agentID;
                            conn.q.bind(conn.exchange, conn.channel);

                            conn.q.subscribe(function (message) {
                                console.log("[MSG] ---> " + JSON.stringify(message));
                                conn.write(JSON.stringify(message) + "\n");
                            }).addCallback(function(ok) {
                                ctag[conn.channel] = ok.consumerTag; });
                        });
                    } catch (err) {
                        console.log("Could not create connection to RabbitMQ. \nStack trace -->" + err.stack);
                    }

                } else if (obj.header == "typing") {

                    var reply = {
                        type: 'chatMsg',
                        msg: utils.escp(obj.msga),
                        visitorNick: obj.channel,
                        customField1: '',
                        time: utils.getDateTime(),
                        channel: obj.channel
                    };

                    conn.exchange.publish('my-queue-'+obj.agentID, reply);
                }

            } catch (err) {
                console.log("ERROR ----> " + err.stack);
            }
        });

        // When the visitor closes or reloads a page we need to unbind from RabbitMQ?
        conn.on('close', function () {
            try {

                // Close the socket
                conn.close();

                // Close RabbitMQ           
               conn.q.unsubscribe(ctag[conn.channel]);

            } catch (er) {
                console.log(":::::::: EXCEPTION SOCKJS (ON-CLOSE) ::::::::>>>>>>> " + er.stack);
            }
        });
    });

答案 6 :(得分:1)

要获得所需的行为,只需让每个使用者从自己的队列中消耗。您必须使用非直接交换类型(主题,标题,扇出)才能立即将消息发送到所有队列。

答案 7 :(得分:1)

我评估你的情况是:

  • 我有一个消息队列(您接收消息的来源,我们将其命名为q111)

  • 我有多个消费者,我希望用相同的消息做不同的事情。

你的问题是这个队列收到3条消息,消费者A消耗消息1,其他消费者B和C消耗消息2和3.你需要设置rabbitmq传递的消息所有这三个消息(1,2,3)的相同副本同时发送给所有三个连接的消费者(A,B,C)。

虽然可以通过许多配置来实现这一目标,但一种简单的方法是使用以下两步概念:

  • 使用动态rabbitmq-shovel从所需队列中拾取消息(q111)并发布到扇出交换(专门为此目的创建并专用的交换)。
  • 现在重新配置您的消费者A,B& C(正在收听队列(q111))直接使用专属& amp;每个消费者的匿名队列。

注意:使用此概念时,不要直接从源队列(q111)消费,因为已消耗的消息不会被铲除到您的扇出交换。

如果您认为这不符合您的确切要求,请随时发布您的建议:-)

答案 8 :(得分:1)

在这种情况下,我在这里的答案中找不到一个有趣的选择。

您可以在一个使用者中对具有“重新排队”功能的消息进行处理,以在另一个使用者中进行处理。 一般来说,这不是正确的方法,但对于某人来说可能就足够了。

https://www.rabbitmq.com/nack.html

当心循环(当所有消耗者都出现nack + requeue消息时)!

答案 9 :(得分:0)

如果您正好像我一样使用amqplib库,那么它们handy examplePublish/Subscribe RabbitMQ tutorial实现可能会让您感到很方便。

答案 10 :(得分:0)

我认为您应该使用扇出交换机检查发送消息。这样你就会收到针对不同消费者的相同消息,RabbitMQ正在为这些新消费者/订阅者中的每一个创建不同的队列。

这是在javascript中查看教程示例的链接 https://www.rabbitmq.com/tutorials/tutorial-one-javascript.html

答案 11 :(得分:0)

显然,出门就是您想要的。 fanout

阅读rabbitMQ教程: https://www.rabbitmq.com/tutorials/tutorial-three-javascript.html

这是我的示例:

Publisher.js:

amqp.connect('amqp://<user>:<pass>@<host>:<port>', async (error0, connection) => {
    if (error0) {
      throw error0;
    }
    console.log('RabbitMQ connected')
    try {
      // Create exchange for queues
      channel = await connection.createChannel()
      await channel.assertExchange(process.env.EXCHANGE_NAME, 'fanout', { durable: false });
      await channel.publish(process.env.EXCHANGE_NAME, '', Buffer.from('msg'))
    } catch(error) {
      console.error(error)
    }
})

Subscriber.js:

amqp.connect('amqp://<user>:<pass>@<host>:<port>', async (error0, connection) => {
    if (error0) {
      throw error0;
    }
    console.log('RabbitMQ connected')
    try {
      // Create/Bind a consumer queue for an exchange broker
      channel = await connection.createChannel()
      await channel.assertExchange(process.env.EXCHANGE_NAME, 'fanout', { durable: false });
      const queue = await channel.assertQueue('', {exclusive: true})
      channel.bindQueue(queue.queue, process.env.EXCHANGE_NAME, '')

      console.log(" [*] Waiting for messages in %s. To exit press CTRL+C");
      channel.consume('', consumeMessage, {noAck: true});
    } catch(error) {
      console.error(error)
    }
});

这是我在互联网上找到的一个例子。也许也可以提供帮助。 https://www.codota.com/code/javascript/functions/amqplib/Channel/assertExchange