RabbitMQ高級特性第3篇-TTL和死信隊列

一、TTL概述

  • TTL 全稱 Time To Live (存活時間/過期時間)。
  • 當消息到達存活時間後,還沒有被消費,就會被自動刪除。
  • RabbitMQ可以對單個消息設置過期時間,也可以對整個隊列設置過期時間。
比如:當用戶下單之後30分鐘不支付就會失效:訂單系統發送一個消息到MQ中30分鐘後用戶還沒有支付就不能再支付了,此時要讓訂單失效

 

二、設置單個消息過期時間 

  • 發送消息時針對單個消息設置過期時間,代碼如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestRabbitMQ {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 發送消息到mq並設置過期時間
    @Test
    public void testSendStr(){
        // 發送消息
        // 參數1:交換機名字
        // 參數2:路由鍵
        // 參數3:消息字符串
        rabbitTemplate.convertAndSend(
                "xkp_topic_exchange",
                "xkp.news",
                "hello rabbitmq111......",message -> {
                    // 設置消息過期時間:單位:毫秒
                    message.getMessageProperties().setExpiration("10000");
                    // 返回消息對象
                    return message;
                });
    }
}

三、設置整個隊列過期時間

方式1:在消息生產方創建隊列時設置

package com.xkp.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitMQConfig {
    // 交換機名稱
    public static final String EXCHANGE_NAME = "xkp_topic_exchange";
    // 隊列名稱
    public static final String QUEUE_NAME = "xkp_queue";

    // 1. 定義交換機
    @Bean("xkpExchange")
    public Exchange createExchange(){
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }

    // 2. 定義隊列
    @Bean("xkpQueue")
    public Queue createQueue(){
        // 創建map集合:封裝隊列參數
        Map<String,Object> map = new HashMap<>();
        // 設置隊列過期時間
        map.put("x-message-ttl", 20000);
        return QueueBuilder.durable(QUEUE_NAME).withArguments(map).build();
    }

    // 3. 隊列與交換機綁定關係
    @Bean
    public Binding bindExchangeAndQueue(@Qualifier("xkpQueue") Queue queue,
                                        @Qualifier("xkpExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("xkp.#").noargs();
    }

}

 

方式2:在消息消費方監聽消息時設置

@Component
public class RabbitMQConfig {
    /**
     * 監聽消息,綁定隊列設置隊列過期時間
     */
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = "xkp_topic_exchange",type = "topic"),
            value = @Queue(
                    value = "xkp_queue",
                    durable = "true",
                    arguments = @Argument( // 設置隊列參數
                            name="x-message-ttl", // 設置隊列過期時間
                            type = "java.lang.Integer",
                            value = "20000")),
            key = "xkp.#"
    ))
    public void handlerMessage(Message message, Channel channel)throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        // 1. 轉換消息
        System.out.println("消息內容: " + new String(message.getBody()));
        // 2. 手工簽收
        channel.basicAck(deliveryTag,true);
    }
}
  • 注意事項
    • 一旦設置隊列過期時間,推薦在消費方和生產方同時指定且值要相同,否則後啓動的一方會出現錯誤。
    • 如果設置了隊列過期時間也設置了消息的過期時間則以時間短的爲準

四、死信隊列

1、什麼是死信隊列

死信隊列英文全稱:Dead Letter Exchange,英文縮寫:DLX。中文名稱:死信交換機,由於其他消息中間件沒有交換機概念,只有RabbitMQ中有交換機的概念,所以習慣稱死信交換機爲死信隊列。

當消息成爲Dead message 後可以重新發送到另一個交換機,這個交換機就是DLX。

2、消息成爲死信的三種情況

  1. 隊列消息數量達到限制
  2. 消費者拒接接收消息:消費者調用basicNack/basicReject方法並且不把消息重新加入原目標隊列,即requeue=false;
  3. 原隊列設置了過期設置,消息到達過期時間未被消費

3、隊列綁定死信交互機

 死信交換機的定義和普通交換機的定義完全相同,隊列綁定死信交換機與綁定普通交換機的方式完全相同,死信交換機就是一個普通的交換機,只是換了一個叫法而已,沒有什麼特殊之處,給隊列綁定死信交換機需要給隊列設置如下兩個參數:

  • x-dead-letter-exchange          死信交換機名稱
  • x-dead-letter-routing-key      發送給死信交換機的routingKey

4、實現代碼

1、定義普通交換機和普通隊列

2、定義死信交換機和死信隊列

3、普通隊列綁定死信交換機


@Configuration
public class RabbitMQConfig {
    // 普通交換機名稱
    public static final String EXCHANGE_NAME = "xkp_topic_exchange";
    // 死信交換機名稱
    public static final String DEAD_EXCHANGE_NAME = "dead_xkp_topic_exchange";

    // 普通隊列名稱
    public static final String QUEUE_NAME = "xkp_queue";

    // 死信隊列名稱
    public static final String DEAD_QUEUE_NAME = "dead_xkp_queue";

    // 1. 定義普通交換機
    @Bean("xkpExchange")
    public Exchange createExchange(){
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }

    // 2. 定義普通隊列
    @Bean("xkpQueue")
    public Queue createQueue(){
        // 創建map集合:封裝隊列參數
        Map<String,Object> map = new HashMap<>();
        // 綁定死信交換機名稱
        map.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
        // 設置發送給死信交換機的routingKey
        map.put("x-dead-letter-routing-key", "dead.xkp.news");
        // 設置隊列過期時間
        map.put("x-message-ttl", 20000);
        // 設置隊列可以存儲的最大消息數量
        map.put("x-max-length", 10);
        return QueueBuilder.durable(QUEUE_NAME).withArguments(map).build();
    }

    // 3. 定義死信交換機
    @Bean("deadXkpExchange")
    public Exchange createDeadExchange(){
        return ExchangeBuilder.topicExchange(DEAD_EXCHANGE_NAME).durable(true).build();
    }

    // 4. 定義死信隊列
    @Bean("deadXkpQueue")
    public Queue createDeadQueue(){
        // 創建map集合:封裝隊列參數
        return QueueBuilder.durable(DEAD_QUEUE_NAME).build();
    }

    // 5. 普通隊列與普通交換機綁定關係
    @Bean
    public Binding bindExchangeAndQueue(@Qualifier("xkpQueue") Queue queue,
                                        @Qualifier("xkpExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("xkp.#").noargs();
    }

    // 6. 死信隊列與死信交換機綁定關係
    @Bean
    public Binding bindDeadExchangeAndQueue(@Qualifier("deadXkpQueue") Queue queue,
                                        @Qualifier("deadXkpExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("dead.xkp.#").noargs();
    }

}

4、在消息生產方編寫測試類

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestRabbitMQ {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 測試死信消息
     */
    @Test
    public void testDLX(){
        // 測試過期時間:過期沒有消費的消息會進入死信隊列
        rabbitTemplate.convertAndSend(
                    "xkp_topic_exchange",
                    "xkp.news",
                    "hello rabbitmq 我是死信消息......" + i);

        // 測試消息數量達到限制:超過數量的消息會進入死信隊列
        for (int i = 0; i < 20 ; i++) {
            // 發送消息
            rabbitTemplate.convertAndSend(
                    "xkp_topic_exchange",
                    "xkp.news",
                    "hello rabbitmq 我是死信消息......" + i);
        }

        // 測試消息拒收:拒收消息會進入死信隊列
        rabbitTemplate.convertAndSend(
                    "xkp_topic_exchange",
                    "xkp.news",
                    "hello rabbitmq 我是死信消息......" + i);
    }
}

5、在消息消費方編寫監聽消息方法:模擬異常然後拒絕簽收消息

@Component
public class RabbitMQConfig {
   
    /**
    * 測試拒絕簽收消息,讓消息進入死信隊列                           
    **/
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = "xkp_topic_exchange",type = "topic"),
            value = @Queue(value = "xkp_queue",durable = "true"),
            key = "xkp.#"
    ))
    public void handlerMessage(Message message, Channel channel)throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            // 1. 轉換消息
            System.out.println("消息內容: " + new String(message.getBody()));
            // 2. 業務處理
            System.out.println("執行業務處理...");
            // 模擬異常
            System.out.println(1/0);
            // 3. 手工簽收
            channel.basicAck(deliveryTag,true);

        } catch (Exception e) {
            // 4. 拒絕簽收
            // 參數3:true:重回隊列,broker會重新發送該消息給消費端,false:不重回隊列,消息會被丟棄或回到死信隊列
            channel.basicNack(deliveryTag,true,false);
        }
    }
    
}

 

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