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


143

我一般只是开始使用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所说的是什么?通常如何配置?

  • 通常这样做吗?我是否应该让交换将消息路由到两个单独的队列中,并由一个使用者使用?


5
我不是RabbitMQ专家。但是,您现在拥有的是队列,但是您想要的是主题,请参阅本教程:rabbitmq.com/tutorials/tutorial-five-python.html,有关队列和主题的更多信息:msdn.microsoft.com/zh-cn /library/windowsazure/hh367516.aspx
UrbanEsc 2012年

1
我相信他确实希望进行扇出,尽管主题也可以正常工作,并且以后会提供更多控制权。
robthewolf 2012年

谢谢@UrbanEsc。主题似乎通过使一条消息进入多个队列来解决该问题,因此每个队列的使用者都将其消耗掉。对于我的特殊情况,这使我更趋向于多个队列/单个使用者场景。
mikemaccana 2012年

1
对于2018年(甚至2016年或更早的版本),答案是使用IMO卡夫卡之类的东西。
WattsInABox

Answers:


115

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

不,如果使用者在同一队列中,则不会。从RabbitMQ的AMQP概念指南中:

重要的是要了解,在AMQP 0-9-1中,消息在使用者之间是负载平衡的。

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

通常这样做吗?我是否应该让交换将消息路由到两个单独的队列中,并由一个使用者使用?

不,不是这样,单个队列/多个使用者,每个使用者处理相同的消息ID是不可能的。让交换将消息路由到两个单独的队列中确实更好。

由于我不需要太复杂的路由,因此扇出交换机可以很好地解决这一问题。由于node-amqp具有“默认交换”的概念,允许您直接将消息发布到连接中,因此我没有过多关注Exchange,但是大多数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
扇出显然是您想要的。这对您无济于事,但我想我要提到的队列中的循环行为是可配置的。 int prefetchCount = 1; channel.basicQos(prefetchCount); 这样一来,每个消费者都可以在收到上一条消息后立即收到一条消息。而不是接收交替的消息。再次不能解决您的问题,但是对人们了解可能很有用。此处的示例http://www.rabbitmq.com/tutorials/tutorial-two-java.html在公平派遣下
省略

3
需要说明的是:“默认交换”不是特定于node-amqp的。这是AMQP的一般概念,具有以下规则:当任何消息发布到默认交换时,路由密钥(与该消息一起发布)被AMQP代理视为队列名称。这样看来您可以直接发布到队列。但是你不是。代理只需将每个队列绑定到具有等于队列名称的路由键的默认交换。
Ruslan Stelmachenko'6

2
在不涉及队列而是多播的情况下,rabbitmq中的Apache activemq jms主题是否可以替代?
pantonis

如果同一用户从多台设备登录,则消息仅获得一台设备。请问该如何解决?
拉菲克

@Rafiq,您应该为此提出问题。
mikemaccana

27

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


25

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

如果希望多个使用者使用同一条消息,请执行以下过程。

在每个队列属性中,创建多个队列(每个接收消息的应用程序一个队列),将路由标记与amq.direct交换“绑定”在一起。将您的发布应用更改为发送到amq.direct并使用路由标签(而不是队列)。然后,AMQP将消息复制到具有相同绑定的每个队列中。奇迹般有效 :)

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

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

这是发送数据而无需发布应用程序知道或关心接收应用程序的理想方式。


7

是的,每个消费者都可以收到相同的消息。看看 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中做到这一点。听起来您想做一个简单的扇出(教程3),它将消息发送到连接到交换机的所有队列。

与您正在做的事情和想要做的事情的区别基本上是,您将要设置和交换或键入扇出。扇出路线会将所有消息发送到所有连接的队列。每个队列都有一个使用者,该使用者可以单独访问所有消息。

是的,通常这样做,这是AMPQ的功能之一。


很好的答案,除了“通常这样做吗?” 我指的是“让每个消费者都收到相同的消息”-这是不常见的(同一队列中的消费者总是轮流轮询)。我的错可能是因为不够清楚。
mikemaccana 2012年

实际上,我敢说这取决于您要使用它的目的。您有两个基本选择发布/订阅队列或工作队列。您最初的设置是工作队列,但您想要的是扇出发布/订阅。他们指出,这里的常用用法完全取决于您要执行的操作。
劫匪

当然可以,但是在工作队列中,相同的消息(例如,相同的消息ID)不会由不同的使用者处理-这是隐式循环。同样,这可能是我的不足够清楚的错。
mikemaccana 2012年

我们似乎在这里是出于交叉目的。
劫匪

对不起,我很困惑。如果有某种方式可以使工作队列中的相同队列中的使用者处理相同的消息ID,请给我一个参考。否则,我将继续相信我在其他地方所读到的内容。
mikemaccana


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);
            }
        });
    });

1

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


1

据我评估,您的情况是:

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

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

您的问题是,当此队列接收到3条消息时,消费者1使用消息1,其他消费者B和C使用消息2和3。同时将这三个消息(1,2,3)发送给所有三个连接的使用者(A,B,C)。

尽管可以进行许多配置来实现此目的,但一种简单的方法是使用以下两步概念:

  • 使用动态Rabbitmq铲子从所需队列(q111)中提取消息并将其发布到扇出交换机(为此专门创建并专用的交换)。
  • 现在,重新配置您的消费者A,B和C(正在监听队列(q111)),以使用每个消费者的专有和匿名队列直接从此Fanout交换中进行监听。

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

如果您认为这不能满足您的确切要求,请随时发表您的建议:-)




-1

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

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

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

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


1
我强烈建议不要这样做,因为它不能以任何方式扩展。消费者没有订单,您无法保证不会重新排队的消费者B在将要处理和重新排队的消费者A之前收到消息,上述循环是一个问题。正如您所说的那样,“通常来说这不是正确的方法”,我想不出一个比其他答案更好的方案。
Kevin Streicher
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.