RabbitMq 死信隊列簡單實現

知識重點

1. RabbitMQ如果產生了消息堆積如何處理
         產生的背景:如果沒有及時的消費者消費消息,生產者一直不斷往隊列服務器存放消息會導致消息堆積
         兩種場景
                1.沒有消費者消費的情況下: 死信隊列、設置消息有效期相當於對我們的消息設置有效期,在規定的時間內如果沒有消                              費的話,自動過期,過期的時候會執行客戶端回調監聽的方法將消息存放到數據庫記錄,後期實現不補償。
                2.有一個消費者消費的情況:應該提高我們的消費者 消費實現集羣

2. RabbitMQ如何徹底保證我們的消息不丟失?
          1.MQ服務器端應該消息持久化到硬盤
          2.生產者使用消息確認機制百分能夠將消息投遞到MQ成功
          3.消費者使用手動acm機制確認消息百分百消費成功


3.如果隊列容量滿了,在繼續投遞可能會丟失 死信隊列
          死信隊列:稱做爲備胎隊列,消息中間件隊列因爲某種消費拒絕存放該消息,可以轉移到死信隊列中存放。
          死信隊列產生的背景
                1.生產者投遞消息到MQ中,消息過期了
                2.隊列的已經達到最大長度(隊列存放消息滿了)MQ拒絕接受存放該消息。
                3.消費者多次消費該消息失敗的情況,也會存放死信。
          死信隊列不能夠和正常隊列存放在同一個服務器中,應該分開服務器存放

簡單搭建死信隊列

  1.聲明訂單和死信交換機、 訂單和死信隊列 、關鍵步驟訂單隊列綁定訂單交換機和死信交換機 、死信隊列綁定死信交換機

@Component
public class DeadLetterMQConfig {
    /**
     * 訂單交換機
     */
    @Value("${mayikt.order.exchange}")
    private String orderExchange;
    /**
     * 訂單隊列
     */
    @Value("${mayikt.order.queue}")
    private String orderQueue;
    /**
     * 訂單路由key
     */
    @Value("${mayikt.order.routingKey}")
    private String orderRoutingKey;
    /**
     * 死信交換機
     */
    @Value("${mayikt.dlx.exchange}")
    private String dlxExchange;
    /**
     * 死信隊列
     */
    @Value("${mayikt.dlx.queue}")
    private String dlxQueue;
    /**
     * 死信路由
     */
    @Value("${mayikt.dlx.routingKey}")
    private String dlxRoutingKey;
    /**
     * 聲明死信交換機
     */
    @Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange(dlxExchange);
    }
    /**
     * 聲明死信隊列
     */
    @Bean
    public Queue dlxQueue() {
        return new Queue(dlxQueue);
    }
    /**
     * 聲明訂單業務交換機
     */
    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange(orderExchange);
    }
    /**
     * 聲明訂單隊列 核心操作一
     */
    @Bean
    public Queue orderQueue() {
        Map<String, Object> arguments = new HashMap<>(2);
        // 綁定我們的死信交換機
        arguments.put("x-dead-letter-exchange", dlxExchange);
        // 綁定我們的路由key
        arguments.put("x-dead-letter-routing-key", dlxRoutingKey);
        return new Queue(orderQueue, true, false, false, arguments);
    }
    /**
     * 綁定訂單隊列到訂單交換機
     */
    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(orderRoutingKey);
    }
    /**
     * 綁定死信隊列到死信交換機
     */
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with(dlxRoutingKey);
    }
}

2.application.properties文件配置 交換機隊列相關名稱和其他配置

server.port=8081
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#開啓駝峯命名  譬如數據庫create_time 自動映射pojo屬性createTime
mybatis.configuration.map-underscore-to-camel-case=true

#配置virtual-host虛擬主機
spring.rabbitmq.virtual-host=/zhang_rabbit
#ip地址
spring.rabbitmq.host=127.0.0.1
#用戶名  密碼
spring.rabbitmq.username=zhang
spring.rabbitmq.password=zhang
#連接端口號
spring.rabbitmq.port=5672
#spring.rabbitmq.publisher-confirm-type=

#模擬演示死信隊列
mayikt.dlx.exchange=mayikt_order_dlx_exchange
mayikt.dlx.queue=mayikt_order_dlx_queue
mayikt.dlx.routingKey=dlx

##備胎交換機
mayikt.order.exchange=mayikt_order_exchange
mayikt.order.queue=mayikt_order_queue
mayikt.order.routingKey=mayikt.order

3.創建producer生產者.並設置消息10秒過期,驗證消息是否加入死信隊列

@RestController
public class DeadLetterController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    /**
     * 訂單交換機
     */
    @Value("${mayikt.order.exchange}")
    private String orderExchange;

    @Autowired
    private OrderMapper orderMapper;
    /**
     * 訂單路由key
     */
    @Value("${mayikt.order.routingKey}")
    private String orderRoutingKey;

//    //方式一
//    @RequestMapping("/sendOrderMsg")
//    public String sendOrderMsg() {
//        rabbitTemplate.convertAndSend(orderExchange,orderRoutingKey,"訂單消息", new MessagePostProcessor(){
//            @Override
//            public Message postProcessMessage(Message message) throws AmqpException {
//                //消息設置過期時間
//                message.getMessageProperties().setExpiration("10000");
//                //消息設置唯一id
//               // message.getMessageProperties().setUserId("10000");
//                return message;
//            }
//        });
//        return "success";
//    }


    //方式二  優化
    @RequestMapping("/sendOrderMsg")
    public String sendOrderMsg() {
        // 1.生產訂單id
        String orderId = System.currentTimeMillis() + "";
        String orderName = "螞蟻課堂第六期報名";
        OrderEntity orderEntity = new OrderEntity(orderName, orderId);
        String msg = JSONObject.toJSONString(orderEntity);
        sendMsg(msg); //rabbitmq生產消息 異步處理
        return orderId;
        // 後期客戶端主動使用orderId調用服務器接口 查詢該訂單id是否在數據庫中存在數據 消費成功 消費失敗
    }

    //rabbit發送消息
    @Async //異步處理
    public void sendMsg(String msg) {
        rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, msg,messagePostProcessor());
        // 消息投遞失敗
    }

    //處理待發送消息
    private MessagePostProcessor messagePostProcessor(){
        return  new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("10000");
                return message;
            }
        };
    }

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

4.創建訂單消費者和死信消費者 

@Component //死信隊列
public class OrderDlxConsumer {

    /**
     * 監聽我們的死信隊列
     */
    @RabbitListener(queues = "mayikt_order_dlx_queue")
    public void orderConsumer(String msg) {
        System.out.println("死信隊列獲取消息:" + msg);
    }
}



@Component
public class OrderConsumer {

    @Autowired
    private OrderMapper orderMapper;

    /**
     * 監聽我們的訂單隊列
     */
    @RabbitListener(queues = "mayikt_order_queue")
    public void orderConsumer(String msg) {
        System.out.println("訂單隊列獲取消息:" + msg);
        OrderEntity orderEntity = JSONObject.parseObject(msg, OrderEntity.class);
        if (orderEntity == null) {
            return;
        }
        orderMapper.addOrder(orderEntity);
    }
}

5.註釋掉訂單消費者,生產消息,發現10秒後,消息轉移到死信隊列由死信隊列消費調 ,放開訂單消費者, 消息則由訂單隊列消費.

6.方式二優化: 生產者異步發送消息到隊列,並返回訂單id. 客戶端根據返回訂單id查詢訂單信息 , 查到表示消費成功 ,查不到表示正在處理或者消費失敗

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