High level part of RabbitMq release confirmation
1. Why is it necessary to release the confirmation advanced part?
In the production environment, RabbitMQ is restarted due to some unknown reasons. During RabbitMQ restart, the delivery of producer messages fails, resulting in message loss, which needs to be processed and recovered manually. So we began to think, how can we reliably deliver RabbitMQ messages? Especially in such extreme cases, when the RabbitMQ cluster is unavailable, how to deal with undeliverable messages?
2 treatment method 1
The callback after the switch exception is completed by implementing the RabbitTemplate.ConfirmCallback interface. However, 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 unreachable, the message will be directly discarded, At this time, the producer does not know that the message is discarded. So how can I get messages that can't be routed to help me find a way to deal with them? Then we can set the mandatory parameter to return the message to the producer when the destination is unreachable during message delivery. The specific operation is completed by implementing rabbittemplate.returncallback
3. Processing method 2 (backup switch)
Concept:
With the mandatory parameter and fallback message, we gain the perception of undeliverable messages, and have the opportunity to find and process producer messages when they cannot be delivered. But sometimes, we don't know how to handle these messages that can't be routed. We can make a log at most, then trigger an alarm, and then handle them manually. It is not elegant to handle these unrouted messages through logs, especially when the producer's service has multiple machines, manual copying logs will be more troublesome and error prone. Moreover, setting the mandatory parameter will increase the complexity of producers and need to add logic to deal with these returned messages. What if you don't want to lose messages and increase the complexity of producers? In the previous article on setting the dead letter queue, we mentioned that the dead letter switch can be set for the queue to store those failed messages, but these non routable messages have no chance to enter the queue, so the dead letter queue cannot be used to save messages. In RabbitMQ, there is a backup switch mechanism, which 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 spare tire for it. When the switch receives a non routable message, it will forward the message to the backup switch for forwarding and processing, Usually, the backup switch is Fanout, so that all messages can be delivered to the bound queue. Then we bind a queue under the backup switch, so that all messages that cannot be routed by the original switch will enter the queue. Of course, we can also establish an alarm queue to monitor and alarm with independent consumers.
When declaring a switch, specify the backup switch through alternate exchange. It is recommended that the backup switch be set to fan out type, or direct or topic type.
However, it should be noted that the routing key when the message is rerouted to the backup switch is the same as that issued from the producer (Fanout has no routing)
4. Code implementation
It needs to be added in the configuration file
spring.rabbitmq.publisher-confirm-type=correlated
⚫ NONE
Disable publish confirmation mode, which is the default
⚫ CORRELATED
The callback method will be triggered after the message is successfully published to the exchange
⚫ SIMPLE
Profile:
server: port: 8080 spring: rabbitmq: host: 192.xxx.xx.xx password: 123456 port: 5672 username: admin publisher-confirm-type: correlated publisher-returns: true
Configuration class
package com.atguigu.rabbitmq.springbootrabbitmq.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; @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 CONFIRM_ROUTING_KEY= "key1"; //Backup switch public static final String BACKUP_EXCHANGE_NAME = "backup.exchange"; //Backup queue public static final String BACKUP_QUEUE_NAME = "backup.queue"; //Warning queue public static final String WARNING_QUEUE_NAME = "warning.queue"; @Bean("confirmExchange") public DirectExchange confirmExchange(){ //If it is not persistent, it will be destroyed automatically return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME).durable(true) .withArgument("alternate-exchange", BACKUP_EXCHANGE_NAME).build(); } //Backup switch @Bean("backupExchange") public FanoutExchange backupExchange(){ return new FanoutExchange(BACKUP_EXCHANGE_NAME); } @Bean("confirmQueue") public Queue confirmQueue(){ //After confirming that the switch cannot forward, pass the message to the backup switch return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build(); } //Backup queue @Bean("backupQueue") public Queue backupQueue(){ return QueueBuilder.durable(BACKUP_QUEUE_NAME).build(); } //Backup warning queue @Bean("warningQueue") public Queue warningQueue(){ return QueueBuilder.durable(WARNING_QUEUE_NAME).build(); } @Bean public Binding confirmExchangeBindconfirmQueue(@Qualifier("confirmQueue")Queue confirmQueue, @Qualifier("confirmExchange")DirectExchange directExchange){ return BindingBuilder.bind(confirmQueue).to(directExchange).with(CONFIRM_ROUTING_KEY); } @Bean public Binding confirmQueueAndbackupExchange(@Qualifier("backupQueue")Queue backupQueue,@Qualifier("backupExchange")FanoutExchange backupExchange){ return BindingBuilder.bind(backupQueue).to(backupExchange); } @Bean public Binding warningQueueAndbackupExchange(@Qualifier("warningQueue")Queue warningQueue,@Qualifier("backupExchange")FanoutExchange backupExchange){ return BindingBuilder.bind(warningQueue).to(backupExchange); } }
Callback interface (inform the producer that the switch or queue did not receive the message correctly, or received the message successfully)
package com.atguigu.rabbitmq.springbootrabbitmq.config; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.ReturnedMessage; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; /** * confirmCallBack: ack is returned when the message arrives at the exchange from the producer, and nack is returned when the message does not arrive at the exchange; * returnCallBack: Called when a message enters exchange but not queue. */ @Slf4j @Component public class MyCallBack implements RabbitTemplate.ConfirmCallback , RabbitTemplate.ReturnsCallback{ @Autowired private RabbitTemplate rabbitTemplate; //Inject the implementation class into the interface, because the ConfirmCallback we implemented is the internal interface of RabbitTemplate. If we don't inject the implementation class, we can't call it @PostConstruct public void init(){ rabbitTemplate.setConfirmCallback(this); rabbitTemplate.setReturnCallback(this); } /** * A callback method for the switch whether it receives a message or not * 1.The switch successfully received the message * CorrelationData Save the ID and related information of the callback message * ack = true The switch received a message * cause null * 2.The switch did not receive the message * CorrelationData Save the ID and related information of the callback message * ack = false The switch received a message * cause Reasons for failure */ @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { String id = correlationData!=null?correlationData.getId():""; if(ack){ log.info("Switch has received id by:{}News of",id); }else{ log.info("The switch has not received id by:{}news,For reasons:{}",id,cause); } } //When the message fails to reach its destination (when it fails to reach the queue, etc.), the message is returned to the producer @Override public void returnedMessage(ReturnedMessage returned) { log.error(" News {}, Switched {} Return, return reason :{}",returned); } }
consumer
package com.atguigu.rabbitmq.springbootrabbitmq.entity; import com.atguigu.rabbitmq.springbootrabbitmq.config.ConfirmConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.stereotype.Component; @Slf4j @Component public class ConfirmConsumer { @RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE_NAME) public void receiveMsg(Message message){ String msg=new String(message.getBody()); log.info("Receive queue confirm.queue news:{}",msg); } }
producer
package com.atguigu.rabbitmq.springbootrabbitmq.controller; import com.atguigu.rabbitmq.springbootrabbitmq.config.ConfirmConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @RequestMapping("/confirm") public class ProducerController { @Autowired private RabbitTemplate rabbitTemplate; @GetMapping("/sendMessage/{message}") public void sendMessage(@PathVariable String message){ CorrelationData correlationData = new CorrelationData("1"); //Correct routing rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,ConfirmConfig.CONFIRM_ROUTING_KEY,message+"key1",correlationData); log.info("Send message content:{}",message+"key1"); //Incorrect route CorrelationData correlationData2 = new CorrelationData("2"); rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,ConfirmConfig.CONFIRM_ROUTING_KEY+"2",message+"key12",correlationData2); log.info("Send message content:{}",message+"key12"); } }