RabbitMQ死信交換器

RabbiteMQ消息的拒絕 —— Reject和Nack中,我們是可以利用Reject和Nack去拒絕消息的,消息在被拒絕的時候是可以指定RabbitMQ是否需要重新發送給消費者。


如何我們消息被拒絕,且不重新投遞的情況下,那麼這條消息就無法被處理了麼?在ActiveMQ死信隊列中,我們介紹過消息失敗被重發一定次數後會進入死信隊列,在RabbitMQ中也有類似的處理——死信交換器。


死信交換器不是默認的設置,這裏是被投遞消息被拒絕後的一個可選行爲,是在創建隊列的時進行聲明的,往往用在對問題消息的診斷上。
在這裏插入圖片描述
消息進入死信交換器一般是以下幾種情況:

  • 消息被拒絕,並且設置requeue參數爲 false
  • 消息過期
  • 隊列達到最大長度

死信交換器仍然只是一個普通的交換器,創建時並沒有特別要求和操作。在創建隊列的時候,聲明該交換器將用作保存被拒絕的消息即可,相關的參數是x-dead-letter-exchange



死信交換器的設置其實和RabbitMQ消息發佈之備用交換器比較類似,不過備用交換器是消息生產者在發佈消息時無法路由消息,消息將被路由到備用交換器,所以備用交換器配置在消息的生產者。而死信交換器則是接收過期或者被拒絕的消息,所以肯定是配置在消息的消費者。


首先我們先看看消息的發佈者,這裏和之前介紹的用法一樣,就是發佈了error、warn、info三條消息,如下:

public class Producer {

    //交換器名稱
    public static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws Exception {
        //創建連接,連接到RabbitMQ
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();

        //創建信道
        Channel channel = connection.createChannel();

        //創建交換器
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC, false, false, null);

        //定義的業務日誌消息級別,即作爲路由鍵使用
        String[] logLevels = {"error", "warn", "info"};
        for (String logLevel : logLevels) {
            String msg = "Hello RabbitMQ";

            //發佈消息,需要參數:交換器、路由鍵,其中以日誌消息級別爲路由鍵
            channel.basicPublish(EXCHANGE_NAME, logLevel, null, msg.getBytes(Charset.forName("UTF-8")));
        }

        channel.close();
        connection.close();
    }
}

然後我們給生產者生成的三條消息,一共創建了2個消費者,一個是可以接收到全部的消息,並且全部拒絕並不重新投遞,其中我們就需要配置其死信交換器了,如下:
在這裏插入圖片描述

public class AllConsumer {

    public static final String EXCHANGE_NAME = "logs";
    public static final String DLX_EXCHANGE_NAME = "dlx";

    public static void main(String[] args) throws Exception {
        //創建連接,連接到RabbitMQ,與發送端一樣
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();

        //創建信道
        Channel channel = connection.createChannel();

        //創建交換器
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC, false, false, null);

        String queueName = "allLog";

        Map<String, Object> argMap = new HashMap<>();
        argMap.put("x-dead-letter-exchange", DLX_EXCHANGE_NAME);

        //創建一個隊列
        channel.queueDeclare(queueName, false, false, false, argMap);

        //將隊列和交換器通過路由鍵進行綁定
        channel.queueBind(queueName, EXCHANGE_NAME, "#");

        //聲明瞭一個消費者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = "Exchange: " + envelope.getExchange() + ", " +
                        "RoutingKey: " + envelope.getRoutingKey() + ", " +
                        "Content: " + new String(body, "UTF-8");
                System.out.println(message);

                channel.basicReject(envelope.getDeliveryTag(), false);
            }
        };

        //消費者正式開始在指定隊列上消費消息
        channel.basicConsume(queueName, false, consumer);
    }
}



另外一個消費者,我們只接收其error消息,並且也是拒絕並不重新投遞,不同的是我們將其送到死信交換器的時候,將其路由鍵進行重新定義了(死信交換器重新定義路由鍵,可以將其不同的消息,發送到同一個隊列之類,可以進行統一處理失敗的消息),代碼如下:
在這裏插入圖片描述

public class ErrorConsumer {

    public static final String EXCHANGE_NAME = "logs";
    public static final String DLX_EXCHANGE_NAME = "dlx";

    public static void main(String[] args) throws Exception {
        //創建連接,連接到RabbitMQ,與發送端一樣
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();

        //創建信道
        Channel channel = connection.createChannel();

        //創建交換器
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC, false, false, null);

        String queueName = "errorLog";

        Map<String, Object> argMap = new HashMap<>();
        argMap.put("x-dead-letter-exchange", DLX_EXCHANGE_NAME);
        //死信路由鍵,會替換消息原來的路由鍵
        argMap.put("x-dead-letter-routing-key", "log_error");

        //創建一個隊列
        channel.queueDeclare(queueName, false, false, false, argMap);

        //將隊列和交換器通過路由鍵進行綁定
        channel.queueBind(queueName, EXCHANGE_NAME, "error");

        //聲明瞭一個消費者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = "Exchange: " + envelope.getExchange() + ", " +
                        "RoutingKey: " + envelope.getRoutingKey() + ", " +
                        "Content: " + new String(body, "UTF-8");
                System.out.println(message);

                channel.basicReject(envelope.getDeliveryTag(), false);
            }
        };

        //消費者正式開始在指定隊列上消費消息
        channel.basicConsume(queueName, false, consumer);
    }
}

上述兩個消費者使用的是同一個死信交換器——dlx,兩個消費者失敗的消息都會進入同一個死信交換器中。當然我們也可以爲每個消費者都定義一個單獨的死信交換器,這樣多個消費者失敗的消息就會進入不同的死信交換器,我們在處理的時候就需要對多個死信交換器進行處理,這裏就需要看其業務的要求的。


另外上述我們發現我們還沒有創建死信交換器(當然我們也是可以在兩個消費者中進行創建的,只要保證其參數相同),這裏我們就是其死信交換器的消費者中進行創建的,這裏同樣我們爲死信交換器也創建了兩個消費者,一個可以消費其中的所有消息,如下:

public class AllDlxConsumer {

    public static final String DLX_EXCHANGE_NAME = "dlx";

    public static void main(String[] args) throws Exception {
        //創建連接,連接到RabbitMQ,與發送端一樣
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();

        //創建信道
        Channel channel = connection.createChannel();

        //創建交換器
        channel.exchangeDeclare(DLX_EXCHANGE_NAME, BuiltinExchangeType.TOPIC, false, false, null);

        String queueName = channel.queueDeclare().getQueue();

        //將隊列和交換器通過路由鍵進行綁定
        channel.queueBind(queueName, DLX_EXCHANGE_NAME, "#");

        //聲明瞭一個消費者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = "Exchange: " + envelope.getExchange() + ", " +
                        "RoutingKey: " + envelope.getRoutingKey() + ", " +
                        "Content: " + new String(body, "UTF-8");
                System.out.println(message);
            }
        };

        //消費者正式開始在指定隊列上消費消息
        channel.basicConsume(queueName, true, consumer);
    }
}

另一個死信交換器消費者只用來處理那個被我們修改了路由鍵的error消息,上述中我們將其路由鍵有error修改爲了log_error,其代碼如下:

public class ErrorDlxConsumer {

    public static final String DLX_EXCHANGE_NAME = "dlx";

    public static void main(String[] args) throws Exception {
        //創建連接,連接到RabbitMQ,與發送端一樣
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();

        //創建信道
        Channel channel = connection.createChannel();

        //創建交換器
        channel.exchangeDeclare(DLX_EXCHANGE_NAME, BuiltinExchangeType.TOPIC, false, false, null);

        String queueName = channel.queueDeclare().getQueue();

        //將隊列和交換器通過路由鍵進行綁定
        channel.queueBind(queueName, DLX_EXCHANGE_NAME, "log_error");

        //聲明瞭一個消費者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = "Exchange: " + envelope.getExchange() + ", " +
                        "RoutingKey: " + envelope.getRoutingKey() + ", " +
                        "Content: " + new String(body, "UTF-8");
                System.out.println(message);
            }
        };

        //消費者正式開始在指定隊列上消費消息
        channel.basicConsume(queueName, true, consumer);
    }
}




然後我運行上述所有代碼,其結果如下,所有消費者受到error、warn、info所有消息,其實都是拒絕的
在這裏插入圖片描述

單獨處理error消息的消費者,只收到其error消息,並拒絕其消息,將其路由鍵修改爲log_error
在這裏插入圖片描述

上述四條消息拒絕都是正常的,所以都進入其死信交換器,我們先查看其消費所有死信交換器的消費者,如下
在這裏插入圖片描述

剩下的死信交換器消費者應該只是消費log_error的消息,如下:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章