消息中間件之RabbitMQ死信隊列

消息中間件之RabbitMQ死信隊列

一、死信隊列

1.1. 簡述

RabbitMQ死信隊列俗稱備胎隊列,消息中間件因爲某種原因拒收該消息後,可以轉移到死信隊列中存放,死信隊列也可以有交換機和路由key等。

1.2. 產生原因:

  • 消息隊列投遞的到MQ中存放,消息已經過期
  • 隊列達到最大的長度,隊列容器已經滿了生產者拒絕接收消息
  • 消費者消費多次消息失敗,就會轉移到死信隊列中

1.3. 注意:

死信隊列最好不和和正常隊列存放在同一個服務器中,應該分開服務器存放。

二、Springboot整合死信隊列

2.1. Maven依賴

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2.2. 配置文件

這是需要先創建一個/longvirtual-host否則啓動 報錯。

spring:
  rabbitmq:
    # 連接地址
    host: 192.168.252.137
    # 端口號
    port: 5672
    # 賬號
    username: guest
    # 密碼
    password: guest
    # 地址
    virtual-host: /long

相關隊列配置

public class RabbitVariable {
	/** 死信交換機 */
    public final static String DLX_EXCHANGE = "long_dlx_exchange";

    /** 死信隊列 */
    public final static String DLX_ORDER_DLX_QUEUE = "long_order_dlx_queue";

    /** 路由key */
    public final static String DLX_ROUTING_KEY = "long_dlx_key";

	/** 訂單交換機 */
    public final static String ORDER_EXCHANGE = "long_order_exchange";

    /** 訂單隊列 */
    public final static String ORDER_QUEUE = "long_order_queue";

    /** 路由key */
    public final static String ORDER_ROUTING_KEY = "long_order_key";

}

2.3. 配置類

@Component
public class RabbitConfig {

    /**
     * 死信交換機
     * @return
     */
    @Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange(RabbitVariable.DLX_EXCHANGE);
    }

    /**
     * 死信隊列
     * @return
     */
    @Bean
    public Queue dlxQueue() {
        return new Queue(RabbitVariable.DLX_ORDER_DLX_QUEUE);
    }

    /**
     * 綁定死信隊列到死信交換機
     * @return
     */
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(dlxQueue())
                .to(dlxExchange())
                .with(RabbitVariable.DLX_ROUTING_KEY);
    }


    /**
     * 訂單業務交換機
     * @return
     */
    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange(RabbitVariable.ORDER_EXCHANGE);
    }

    /**
     * 聲明訂單隊列
     * @return
     */
    @Bean
    public Queue orderQueue() {
        // 訂單隊列綁定到我們的死信交換機
        Map<String, Object> arguments = new HashMap<>(2);
        // 綁定我們的死信交換機
        arguments.put("x-dead-letter-exchange", RabbitVariable.DLX_EXCHANGE);
        // 綁定我們的路由key
        arguments.put("x-dead-letter-routing-key", RabbitVariable.DLX_ROUTING_KEY);
        return new Queue(RabbitVariable.ORDER_QUEUE, true, false, false, arguments);
    }

    /**
     * 綁定訂單隊列到訂單交換機
     * @return
     */
    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue())
                .to(orderExchange())
                .with(RabbitVariable.ORDER_ROUTING_KEY);
    }



}

2.4. 死信隊列消費者

/**
 * @author 墨龍吟
 * @version 1.0.0
 * @ClassName OrderDlxConsumer.java
 * @Description 死信消費者
 * @createTime 2020年03月02日 - 17:36
 */
@Component
public class OrderDlxConsumer {

    @RabbitListener(queues = RabbitVariable.DLX_ORDER_DLX_QUEUE)
    public void orderConsumer(String msg) {
        System.out.println("死信隊列消費訂單消息" + msg);
    }

}

2.5. 訂單消費者

在測試死信對列的時候將代碼註釋掉就可以

@Component
public class OrderConsumer {

    /**
     * 監聽隊列回調的方法
     *
     * @param msg
     */
    @RabbitListener(queues = RabbitVariable.ORDER_QUEUE)
    public void orderConsumer(String msg) {
        System.out.println("正常訂單消費者消息:" + msg);
    }

}

2.6. 控制器發送消息

/**
     * 投遞消息,失效時間10s    Expiration  時間單位是ms
     * @return
     */
    @GetMapping("/sendMsg")
    public String sendMsg() {
        String msg = "發送消息" + UUID.randomUUID();
        System.out.println("發送消息:" + msg);
        rabbitTemplate.convertAndSend(RabbitVariable.ORDER_EXCHANGE, RabbitVariable.ORDER_ROUTING_KEY, msg, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("10000");
                return message;
            }
        });
        return "success";
    }

2.7. 結果

三、RabbitMQ解決消息堆積

產生的背景:如果沒有及時的消費者消費消息,生產者一直不斷往隊列服務器存放消息會導致消息堆積。

對應的場景:

  1. 沒有消費者消費的情況下:通過死信隊列、設置消息有效期(相當於對我們的消息設置有效期,在規定的時間內如果沒有消費的話,自動過期,過期的時候會執行客戶端回調監聽的方法將消息存放到數據庫記錄,後期實現不補償。)
  2. 有一個消費者消費的情況:應該提高我們的消費者,實現集羣

四、消息中間件獲取消費結果

解決方案: 異步+主動查詢

簡單實現:(對第二部分代碼就行修改就可以)

4.1. 修改控制器

@GetMapping("/sendMsg")
public OrderEntity sendOrderMsg() {
    // 訂單實體
    OrderEntity entity = new OrderEntity(UUID.randomUUID().toString(), "訂單消息實體");
    String jsonString = JSONObject.toJSONString(entity);
    sendMsg(jsonString);
    // 返回訂單實體,或者訂單ID
    return entity;
}

/** 異步投遞消息(使用同的話可以,使用ACK確認機制,但是效率比較低) */
@Async
public void sendMsg(String msg) {
    rabbitTemplate.convertAndSend(RabbitVariable.ORDER_EXCHANGE, RabbitVariable.ORDER_ROUTING_KEY, msg, new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setExpiration("10000");
            return message;
        }
    });
    // 投遞失敗可以將失敗的訂單信息存儲到專門的表中去記錄
}

/**
 * 1. 客戶端主動查詢接口
 * 2. 查詢(投遞失敗表)該訂單的消息是否投遞失敗
 * 3. 在查詢訂單數據庫
 */
@RequestMapping("getOrder")
public Object getOrder(String orderId) {
    OrderEntity orderEntity = orderMapper.getOrder(orderId);
    if (orderEntity == null) {
        return "消息正在異步的處理中";
    }
    return orderEntity;
}

4.2. 訂單消費者

@Component
public class OrderConsumer {

    /**
     * 監聽隊列回調的方法
     *
     * @param msg
     */
    @RabbitListener(queues = RabbitVariable.ORDER_QUEUE)
    public void orderConsumer(String msg) {
        System.out.println("正常訂單消費者消息:" + msg);
        OrderEntity orderEntity = JSONObject.parseObject(msg, OrderEntity.class);
        if (orderEntity == null) {
            return;
        }
        // 保存到數據庫
    }

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