【问题标题】:RabbitMQ dead letter exchange never getting messagesRabbitMQ 死信交换永远不会收到消息
【发布时间】:2014-03-11 14:56:31
【问题描述】:

我正在尝试设置我的第一个 RabbitMQ 死信交换,以下是我通过 Web 管理界面使用的步骤:

  1. 使用名称“dead.letter.test”创建新的 DIRECT 交换
  2. 创建新队列“dead.letter.queue”
  3. 将“dead.letter.queue”绑定到“dead.letter.test”
  4. 创建新队列“test1”,将死信交换设置为“dead.letter.test”
  5. 向“test1”发送消息
  6. Nack(requeue = false)“test1”中的消息

我期望这些步骤应该通过“dead.letter.test”交换将记录放入“dead.letter.queue”。这没有发生。

我可以手动将消息放入“dead.letter.test”交换中,它会显示在“dead.letter.queue”中,所以我知道这很好。

当我查看管理 UI 时,它显示 DLX 参数已设置在队列“test1”上。

我哪里错了?

【问题讨论】:

  • 你使用什么路由键?

标签: rabbitmq dead-letter


【解决方案1】:

对于那些使用 Spring-AMQP 的人

在我的情况下,问题是不同的。我希望死信交换是直接类型的。我为队列设置了x-dead-letter-exchange 和x-dead-letter-routing-key。另外我在application.properties 中有spring.rabbitmq.listener.simple.default-requeue-rejected=false。

似乎一切都很好,但在调试时我注意到我的SimpleRabbitListenerContainerFactory 有defaultRequeueRejected 为空。所以原因是当你在@Configuration 中声明SimpleRabbitListenerContainerFactory 时,你创建了一个新的“非默认”bean。默认值是在您的属性中为您在幕后创建的。但是你的SimpleRabbitListenerContainerFactory在@Config里面,这些属性是没有读取的,必须自己读取并在java代码中设置。

这发生在我身上,因为我只是在想要配置并发时从the Spring-AMQP docs 复制粘贴了配置。但是你应该在一个地方做所有事情,无论是在属性中,比如

spring.rabbitmq.listener.simple.default-requeue-rejected=false
spring.rabbitmq.listener.simple.concurrency=5
spring.rabbitmq.listener.simple.max-concurrency=10

或者完全在java中,比如

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setConcurrentConsumers(5);
        factory.setMaxConcurrentConsumers(10);
        factory.setDefaultRequeueRejected(false);
        return factory;
    }

上面这2个是一样的。

我希望当我使用第二个(java)选项时,仍然会从 application.properties 中获取属性,然后我在 java 中进行自定义,但它不会像这样工作。 是的,“复制粘贴”是邪恶的:)

【讨论】:

    【解决方案2】:

    在我的情况下,问题是因为队列有

    ackMode="MANUAL"
    

    但我从未设置它(因为运行时异常),改为使用默认 ACK。

    【讨论】:

      【解决方案3】:

      如果不是强制性的,则不需要创建 FANOUT 交换。

      您可以使用已用于其他交换的相同路由密钥创建 DIRECT 交换。而且也不需要为新的交换创建一个新的队列。您可以将现有队列与新交换一起使用。您只需要将新的交换与队列绑定。

      这是我的 receive.js 文件:

      var amqp = require("amqplib/callback_api");
      var crontab = require('node-crontab');
      
      amqp.connect("amqp://localhost", function (err, conn) {
      conn.createChannel(function (err, ch) {
          var ex = 'direct_logs';
          var ex2 = 'dead-letter-test';
          var severity = 'enterprise-1-key';
      
          //assert "direct" exchange
          ch.assertExchange(ex, 'direct', { durable: true });
          //assert "dead-letter-test" exchange
          ch.assertExchange(ex2, 'direct', { durable: true });
      
          //if acknowledgement is nack() then message will be stored in second exchange i.e. ex2="dead-letter-test"
          ch.assertQueue('enterprise-11', { exclusive: false, deadLetterExchange: ex2 }, function (err, q) {
              var n = 0;
              console.log(' [*] Waiting for logs. To exit press CTRL+C');
              console.log(q);
      
              //Binding queue with "direct_logs" exchange
              ch.bindQueue(q.queue, ex, severity);
              //Binding the same queue with "dead-letter-test"
              ch.bindQueue(q.queue, ex2, severity);
      
              ch.consume(q.queue, function (msg) {
                  // consume messages via "dead-letter-exchange" exchange at every second.
                  if (msg.fields.exchange === ex2) {
                      crontab.scheduleJob("* * * * * *", function () {
                          console.log("Received by latest exchange %s", msg.fields.routingKey, msg.content.toString());
                      });
                  } else {
                      console.log("Received %s", msg.fields.routingKey, msg.content.toString());
                  }
      
                  if (n < 1) {
                      // this will executes first time only. Here I'm sending nack() so message will be stored in "deadLetterExchange"
                      ch.nack(msg, false, false);
                      n += 1;
                  } else {
                      ch.ack(msg)
                      n = 0
                  }
              }, { noAck: false });
          });
        });
      });
      

      【讨论】:

        【解决方案4】:

        如果您希望所有队列都有相同的死信交换,那么设置一般策略会更容易:

        sudo rabbitmqctl -p /my/vhost/path set_policy DLX ".*" '{"dead-letter-exchange":"MyExchange.DEAD"}' --apply-to queues
        

        【讨论】:

        • 我真的推荐这种方法,因为它更加灵活,并且是 rabbitmq 团队推荐的方法。我实现了这两种方法,而策略一种则减少了复杂的代码,并且可以很容易地动态更改行为,而无需备份和重新创建队列。在这里了解为什么应该使用策略:rabbitmq.com/parameters.html#policies
        • 我已经尝试了交换和队列级别的策略,并且消息永远不会在我的死信交换中结束,尽管如果我在声明队列时通过参数声明它工作正常。我同意 ^^,这是一个更好的解决方案,尤其是因为当我想更新策略时,我不必重新声明/重新创建/迁移队列。
        【解决方案5】:

        没有路由密钥和直接交换的死信交换


        请按照以下步骤进行操作:-
        1. 创建一个名为“dead_queue”的新队列。
        2. 创建一个名为“dead_exchange”的交换,交换类型应为“直接”。
        3. 绑定 'dead_queue' 和 'dead_exchange' 没有路由键。
        4. 创建一个名为 'test_queue' 的新队列,并将其 'x-dead-letter-exchange' 名称设置为 'dead_exchange'
        5. 创建一个名为“test_exchange”的交换,交换类型应为“直接”
        6. 绑定 'test_exchange' 和 'test_queue' 没有路由键。

        最后我们会检查它。为此,在 'test_exchange' 上发布一些内容,参数 'expiration' 设置为 10000。此后,当消息在 'test_exchange' 上发布时将转到“test_queue”,当消息在队列中过期时,它将在那里查找 DLX 参数(死信交换名称),该消息找到名称“dead_exchange ' 然后该消息将到达 'dead_exchange' 并将其传递到 'dead queue' .. 如果您对此仍有任何问题,并且如果我错过了理解您的问题...写下您的问题,我一定会查看它...谢谢..

        注意: 必须在 'test_exchange' 上发布消息,因为 test_queue 和 test_exchange 绑定没有路由密钥,它会正常工作,但如果您在 'test_queue' 将使用默认的交换和路由键。然后在消息队列到期后尝试使用一些默认路由键将死消息传递到 dead_exchange,并且消息不会进入该队列。

        【讨论】:

        • 你有任何node js 代码供参考,我正在尝试找到在哪里定义dead_exchange 的发布者?我已经设置了话题交流。
        • @user269867 当然,我会调查的。因为现在我正在研究节点 js。我们可以直接通过Skype聊天sahil.gulati1991@outlook.com
        【解决方案6】:

        Gentilissimo Signore 在 Twitter 上回答了我的问题。问题是,如果您的死信交换设置为 DIRECT,您必须指定死信路由键。如果您只想将所有 NACKed 消息放入死信桶以供以后调查(就像我一样),那么您的死信交换应该设置为 FANOUT。

        以下是有效的更新步骤:

        1. 使用名称“dead.letter.test”创建新的 FANOUT 交换
        2. 创建新队列“dead.letter.queue”
        3. 将“dead.letter.queue”绑定到“dead.letter.test”
        4. 创建新队列“test1”,将死信交换设置为“dead.letter.test”
        5. 向“test1”发送消息
        6. Nack(requeue = false)“test1”中的消息

        【讨论】:

        • 如果我想要一个单次交换的死信队列并根据路由键重定向怎么办。扇出可以吗?
        • K-lyer 问题的解决方案:关于死信交换使用的唯一“特殊”之处在于您将它与 x-dead-letter-exchange 和可选的 x-dead-letter-routing 绑定-关键属性。如果您想在路由键上重定向,只需将其设为直接交换,并将队列绑定到它。
        • 对于那些希望它与直接交换和单个队列一起工作的人,同时绑定队列并将队列名称添加为路由键属性
        【解决方案7】:

        如果你想在死信交换中使用自定义路由键,你必须在声明工作队列时设置x-dead-letter-routing-key(在你的情况下是test1),否则将使用默认路由键。在您的情况下,RabbitMQ 代理检测到循环并简单地丢弃被拒绝的消息。

        您需要在test1 队列上设置x-dead-letter-exchange=dead.letter.test 和x-dead-letter-routing-key=dead.letter.queue 参数。

        【讨论】:

        • Zaq,感谢您的回复,我尝试添加 x-dead-letter-exchange 和 x-dead-letter-routing-key 但我仍然没有收到 nacked 消息进入死信交换。我的目标很简单:“test1”队列中被 nack 的任何消息都将被放入“dead.letter.test”交换,然后附加到该交换的任何队列都将接收该消息。我是否需要消息上的自定义路由键来完成此操作?
        • 指定用于处理 amqp 代理的问题语言和库。并添加一些重现您的问题的代码。还要指定 RabbitMQ 版本。
        • 另外,设置Alternate Exchange,以防您的消息无法路由。一种可能的解决方案是尝试 FANOUT 交换,这肯定会将消息路由到任何地方。
        • @jhilden 你解决了你的问题吗?我有同样的问题
        【解决方案8】:

        使用名称“dead.letter.test”创建新的 DIRECT 交换

        正确

        创建新队列“dead.letter.queue”

        正确

        将“dead.letter.queue”绑定到“dead.letter.test”

        正确

        创建新队列“test1”,将死信交换设置为“dead.letter.test”

        我假设您正在创建 test1 队列并将其绑定到 dead.letter.test 交换

        向“test1”发送消息

        如果您希望 dead.letter.queue 接收您的消息,则必须在发送消息时提供路由密钥,并且使用 dead.letter.queue 的客户端也应该使用相同的路由密钥

        如果您在没有路由密钥的情况下发布,那么只有订阅了 test1 的客户端才会收到消息。

        如果您将消息发布到 direct.letter.test 交换,那么所有队列都会收到该消息。它将像扇出交换一样工作

        因此,如果您希望 dead.letter.queue 接收消息,则必须在该队列中发布消息,否则您必须在发布和订阅以及发布消息以进行交换时使用相同的路由密钥

        【讨论】:

        • Atul,“test1”队列没有绑定到任何交换,我只是为了测试目的直接发布到它。我想要的是,当来自“test1”的任何消息被 nack 时,该消息将被放入“dead.letter.test”交换中。
        • 您必须将其绑定到 dead.letter.test。顺便说一句,您是如何创建队列而不提及哪个交易所的?
        • 我猜,如果你没有提到交换,那么你的队列会自动绑定到默认交换,所以你的消息会被发布到默认交换
        • Atul,你是对的,如果你不说队列在什么交换上(这是有效的),那么当你直接向队列发送消息时,它会通过默认交换。不管怎样,如果一条消息在队列中被 nack 了(不管它是如何进入该队列的),它不应该被发送到与原始队列(test1)关联的死信队列吗?
        • RabbitMQ 中消息传递模型的核心思想是生产者从不直接向队列发送任何消息。实际上,生产者通常根本不知道消息是否会被传递到任何队列。相反,生产者将消息发送到交换器。交换是一件非常简单的事情,它接收来自生产者的消息并将它们推送到队列中。交换必须确切地知道如何处理它收到的消息。是否应该将其附加到特定队列?它应该附加到许多队列中吗?或者它应该被丢弃。
        猜你喜欢
        • 2012-11-17
        • 2015-07-24
        • 2012-12-17
        • 2015-04-22
        • 2017-09-10
        • 2022-06-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多