RabbitMQ Learning (10): Release Confirmation Advanced

I. Overview

In the production environment, due to some unknown reasons, RabbitMQ restarted. During the restart of RabbitMQ, the delivery of producer messages failed, resulting in message loss, which required manual processing and recovery. In such an extreme situation, when the RabbitMQ cluster is unavailable, how to deal with undeliverable messages? How can RabbitMQ's message be delivered reliably?

2. Release and confirm the springboot version

2.1 Confirmation Mechanism Scheme

That is, the cache is introduced to cache the messages, and the scheduled tasks re-deliver the unsuccessfully sent messages. When the switch receives the message, it can be cleared from the cache.

2.2 Code Architecture Diagram

2.3 Configuration file

In the configuration file, you need to add

spring.rabbitmq.publisher-confirm-type=correlated

  • NONE disables publish confirm mode, is the default

  • CORRELATED The callback method will be triggered after the message is successfully published to the exchange

  • SIMPLE has been tested to have two effects. One effect is the same as the CORRELATED value and triggers the callback method. The second is to use the rabbitTemplate to call the waitForConfirms or waitForConfirmsOrDie method after the message is successfully published, waiting for the broker node to return the sending result, and determine the logic of the next step based on the returned result , the point to note is that if the waitForConfirmsOrDie method returns false, the channel will be closed, and then the message cannot be sent to the broker

spring.rabbitmq.host=182.92.234.71
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123
spring.rabbitmq.publisher-confirm-type=correlated

2.4 Configuration class

@Configuration
public class ConfirmConfig {
     public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
     public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
     //声明业务 Exchange
     @Bean("confirmExchange")
     public DirectExchange confirmExchange(){
         return new DirectExchange(CONFIRM_EXCHANGE_NAME);
     }
     // 声明确认队列
     @Bean("confirmQueue")
     public Queue confirmQueue(){
         return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
     }
     // 声明确认队列绑定关系
     @Bean
     public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
     @Qualifier("confirmExchange") DirectExchange exchange){
         return BindingBuilder.bind(queue).to(exchange).with("key1");
     }
}

2.5 Producers

@RestController
@RequestMapping("/confirm")
@Slf4j
public class Producer {
     public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
     @Autowired
     private RabbitTemplate rabbitTemplate;
     @Autowired
     private MyCallBack myCallBack;
     //依赖注入 rabbitTemplate 之后再设置它的回调对象
     @PostConstruct
     public void init(){
         rabbitTemplate.setConfirmCallback(myCallBack);
     }

     @GetMapping("sendMessage/{message}")
     public void sendMessage(@PathVariable String message){
        //指定消息 id 为 1
        CorrelationData correlationData1=new CorrelationData("1");
        String routingKey="key1";        
        rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME,routingKey,
                                         message+routingKey,correlationData1);

        CorrelationData correlationData2=new CorrelationData("2");
        routingKey="key2";        
        rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME,routingKey,
                                        message+routingKey,correlationData2);
         log.info("发送消息内容:{}",message);
     }
}

2.6 Callback interface

@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback {
     /**
     * 交换机不管是否收到消息的一个回调方法
     * CorrelationData
     * 消息相关数据
     * ack
     * 交换机是否收到消息
     */
     @Override
     public void confirm(CorrelationData correlationData, boolean ack, String cause) {
         String id = correlationData!=null ? correlationData.getId() : "";
         if(ack){
             log.info("交换机已经收到 id 为:{}的消息",id);
         }else{
             log.info("交换机还未收到 id 为:{}消息,由于原因:{}",id,cause);
         }
     }
}

2.7 Consumers

@Component
@Slf4j
public class ConfirmConsumer {
     public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
     @RabbitListener(queues =CONFIRM_QUEUE_NAME)
     public void receiveMsg(Message message){
         String msg=new String(message.getBody());
         log.info("接受到队列 confirm.queue 消息:{}",msg);
     }
}

3. Rollback message

3.1 Mandatory parameters

When only the producer confirmation mechanism is enabled, the switch will directly send a confirmation message to the message producer after receiving the message. If the message is found to be unroutable, the message will be discarded directly. At this time, the producer does not know the message was dropped for this event.

By setting the mandatory parameter, the message can be returned to the producer when the destination is unreachable during message delivery.

3.2 Producers

@Slf4j
@Component
public class MessageProducer implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
     @Autowired
     private RabbitTemplate rabbitTemplate;
     //rabbitTemplate 注入之后就设置该值
     @PostConstruct
     private void init() {
         rabbitTemplate.setConfirmCallback(this);
         /**
         * true:
         * 交换机无法将消息进行路由时,会将该消息返回给生产者
         * false:
         * 如果发现消息无法进行路由,则直接丢弃
         */
         rabbitTemplate.setMandatory(true);
         //设置回退消息交给谁处理
         rabbitTemplate.setReturnCallback(this);
     }

    @GetMapping("sendMessage")
    public void sendMessage(String message){
        //让消息绑定一个 id 值
        CorrelationData correlationData1 = 
                            new CorrelationData(UUID.randomUUID().toString());   
        rabbitTemplate.convertAndSend("confirm.exchange","key1",message+"key1",
                                        correlationData1);
        log.info("发送消息 id 为:{}内容为{}",correlationData1.getId(),message+"key1");    
        CorrelationData correlationData2 = 
                            new CorrelationData(UUID.randomUUID().toString());   
        rabbitTemplate.convertAndSend("confirm.exchange","key2",message+"key2",
                                        correlationData2);
        log.info("发送消息 id 为:{}内容为{}",correlationData2.getId(),message+"key2");
    }
    
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (ack) {
            log.info("交换机收到消息确认成功, id:{}", id);
        } else {
            log.error("消息 id:{}未成功投递到交换机,原因是:{}", id, cause);
        }
    }
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, 
                                    String exchange, String routingKey) {
        log.info("消息:{}被服务器退回,退回原因:{}, 交换机是:{}, 路由 key:{}",
        new String(message.getBody()),replyText, exchange, routingKey);
    }
}

3.3 Callback interface

@Component
@Slf4j
public class MyCallBack implements
RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
    /**
     * 交换机不管是否收到消息的一个回调方法
     * CorrelationData
     * 消息相关数据
     * ack
     * 交换机是否收到消息
    */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id=correlationData!=null?correlationData.getId():"";
        if(ack){
            log.info("交换机已经收到 id 为:{}的消息",id);
        }else{
            log.info("交换机还未收到 id 为:{}消息,由于原因:{}",id,cause);
        }
    }

    //当消息无法路由的时候的回调方法
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String
    exchange, String routingKey) {
    log.error(" 消 息 {}, 被交换机 {} 退回,退回原因 :{}, 路 由 key:{}",
                    new String(message.getBody()),exchange,replyText,routingKey);
    }
}

4. Backup switch

With the mandatory parameter and the fallback message, we gain awareness of undeliverable messages, and have the opportunity to detect and process when a producer's message cannot be delivered. But sometimes, we don't know how to deal with these messages that cannot be routed. At most, we can make a log, then trigger an alarm, and then handle it manually. It is very inelegant to process these unroutable messages through logs, especially when the producer's service has multiple machines, manually copying logs will be more troublesome and error-prone. And setting the mandatory parameter will increase the complexity of the producer, and it is necessary to add logic to handle these returned messages. How to do it if you don't want to lose messages and don't want to increase the complexity of the producer?

In the previous article on setting up the dead letter queue, we mentioned that a dead letter switch can be set for the queue to store messages that fail to be processed, but these unroutable messages have no chance to enter the queue at all, so the dead letter queue cannot be used to save them information.

In RabbitMQ, there is a backup switch mechanism that can deal with this problem well. What is a backup switch? The backup switch can be understood as the "spare tire" of the switch in RabbitMQ. When we declare a corresponding backup switch for a switch, we create a backup switch for it. When the switch receives a non-routable message, it will pass this The message is forwarded to the backup switch, which is forwarded and processed by the backup switch. Usually the type of the backup switch is Fanout, so that all messages can be delivered to the queue bound to it, and then we bind a queue, so that all messages that cannot be routed by the original switch will enter this queue. Of course, we can also build an alarm queue and use independent consumers to monitor and alarm.

4.1 Code Architecture Diagram

For messages that cannot be delivered, put them into the monitoring queue for monitoring, and put them into the alarm queue for alarm.

4.2 Configuration class

@Configuration
public class ConfirmConfig {
     public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
     public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
     public static final String BACKUP_EXCHANGE_NAME = "backup.exchange";
     public static final String BACKUP_QUEUE_NAME = "backup.queue";
     public static final String WARNING_QUEUE_NAME = "warning.queue";

     // 声明确认队列
     @Bean("confirmQueue")
     public Queue confirmQueue(){
         return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
     }

     //声明确认队列绑定关系
     @Bean
     public Binding queueBinding(@Qualifier("confirmQueue") Queue queue, @Qualifier("confirmExchange") DirectExchange exchange){
         return BindingBuilder.bind(queue).to(exchange).with("key1");
     }

     //声明备份 Exchange
     @Bean("backupExchange")
     public FanoutExchange backupExchange(){
         return new FanoutExchange(BACKUP_EXCHANGE_NAME);
     }

     //声明确认 Exchange 交换机的备份交换机
     @Bean("confirmExchange")
     public DirectExchange confirmExchange(){
         ExchangeBuilder exchangeBuilder = ExchangeBuilder
        .directExchange(CONFIRM_EXCHANGE_NAME).durable(true)
        //设置该交换机的备份交换机
        .withArgument("alternate-exchange", BACKUP_EXCHANGE_NAME);

        return (DirectExchange)exchangeBuilder.build();
     }

     // 声明警告队列
     @Bean("warningQueue")
     public Queue warningQueue(){
         return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
     }

     // 声明报警队列绑定关系
     @Bean
     public Binding warningBinding(@Qualifier("warningQueue") Queue queue, @Qualifier("backupExchange") FanoutExchange backupExchange){
         return BindingBuilder.bind(queue).to(backupExchange);
     }

     // 声明备份队列
     @Bean("backQueue")
     public Queue backQueue(){
         return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
     }

     // 声明备份队列绑定关系
     @Bean
     public Binding backupBinding(@Qualifier("backQueue") Queue queue, @Qualifier("backupExchange") FanoutExchange backupExchange){
         return BindingBuilder.bind(queue).to(backupExchange);
     }
}

4.3 Alert consumers

@Component
@Slf4j
public class WarningConsumer {
    public static final String WARNING_QUEUE_NAME = "warning.queue";
    @RabbitListener(queues = WARNING_QUEUE_NAME)
    public void receiveWarningMsg(Message message) {
        String msg = new String(message.getBody());
        log.error("报警发现不可路由消息:{}", msg);
    }
}

4.4 Priority of backup switches and mandatory parameters

When the mandatory parameter and the backup switch can be used together, if both are enabled at the same time, the result shows that the priority of the backup switch is higher.

Guess you like

Origin blog.csdn.net/m0_49499183/article/details/129163860
Recommended