[RabbitMQ] message reliability

Posted by rawky on Mon, 24 Jan 2022 10:01:55 +0100

After the Producer sends a message, various accidents may occur in the process of message delivery, resulting in the Consumer being unable to receive the message correctly. RabbitMQ has also introduced some solutions to deal with this situation.

1. Message reliability delivery

After the Producer sends a message, it will be delivered through the following link:
Producer - > Exchange - > Queue - > Consumer
In this process, message sending failure may occur in the process of producers sending messages to switches, switches sending messages to queues, and queues sending messages to consumers. So how can we take some measures when there are problems in message delivery? RabbitMQ gives the Confirm mode and Return mode to deal with the problem of messages from the producer to the queue.

1.1 Confirm mode

The confirm mode is used to deal with exceptions in the process of processing messages from the producer to the switch. When the confirm mode is turned on, the message is delivered from the producer to the switch, and a boolean response code will be returned. If it is true, it means that the message has been successfully delivered. If it is false, it means that the message has not been successfully delivered. At this time, you can obtain the failure reason and deal with it accordingly.
confirm mode is opened through publisher confirms = true. Taking Spring as an example, set publisher confirms = true when creating a connection factory, as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/rabbit
        http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">


    <!-- Create connection project -->
    <!-- publisher-confirms open confirm pattern -->
    <context:property-placeholder location="classpath:rabbitmq.yaml"/>
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}" port="${rabbitmq.port}"
                               username="${rabbitmq.username}" password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
                               publisher-confirms="true"
    />

    <rabbit:admin connection-factory="connectionFactory"/>
    <!-- routing pattern -->
    <rabbit:queue id="spring_direct_queue_confirm" name="spring_direct_queue_confirm" auto-declare="true"></rabbit:queue>
    <rabbit:queue id="spring_direct_queue_return" name="spring_direct_queue_return" auto-declare="true"></rabbit:queue>
    <rabbit:direct-exchange id="spring_direct_exchange" name="spring_direct_exchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding queue="spring_direct_queue_confirm" key="confirm"></rabbit:binding>
            <rabbit:binding queue="spring_direct_queue_return" key="return"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!--definition rabbitTemplate Object operation can easily send messages in code-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>

Through rabbittemplate The setconfirmcallback method uses the confirm mode to handle the failure of sending messages from the producer to the switch.

@org.junit.Test
public void test01(){
	rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
		/**
		* confirm Mode callback function
		* @param correlationData Related configuration information
		* @param b Answer: true indicates successful sending, and false indicates failed sending
		* @param s Failure reason: the sending success is null. There are specific reasons for sending failure
		*/
		@Override
		public void confirm(CorrelationData correlationData, boolean b, String s) {
			if(b){
				System.out.println("send successfully,cause: "+s);
			}else{
				System.out.println("send fail, cause: "+s);
			}
		}
	});        
	rabbitTemplate.convertAndSend("spring_direct_exchange","confirm","fanout-queue1-test confirm");
}

1.2. Return mode

Return mode is used to handle exceptions in the process of sending warranty messages from the switch to the queue. The return mode is set by publisher returns = true. Different from the Confrim mode, the return mode can only be called when the message fails to be sent. It will not be called if the message is successfully sent.
The code is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/rabbit
        http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">


    <!-- Create connection project -->
    <!-- publisher-confirms open confirm pattern -->
    <context:property-placeholder location="classpath:rabbitmq.yaml"/>
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}" port="${rabbitmq.port}"
                               username="${rabbitmq.username}" password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
                               publisher-returns="true"
    />

    <rabbit:admin connection-factory="connectionFactory"/>
    <!-- routing pattern -->
    <rabbit:queue id="spring_direct_queue_confirm" name="spring_direct_queue_confirm" auto-declare="true"></rabbit:queue>
    <rabbit:queue id="spring_direct_queue_return" name="spring_direct_queue_return" auto-declare="true"></rabbit:queue>
    <rabbit:direct-exchange id="spring_direct_exchange" name="spring_direct_exchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding queue="spring_direct_queue_confirm" key="confirm"></rabbit:binding>
            <rabbit:binding queue="spring_direct_queue_return" key="return"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!--definition rabbitTemplate Object operation can easily send messages in code-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>

Through rabbittemplate Setreturncallback calls the return mode. And through rabbittemplate Setmandatory (true) sets the processing mode of failure messages. If not set, failure messages will be discarded.

public void test02(){
	//Set the mode for the switch to process failure messages. Messages that are not set will be discarded
	rabbitTemplate.setMandatory(true);
	
	rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
		/**
		* ReturnCallback Parameter description of
		* @param message Message object
		* @param replyCode Error code
		* @param replyText error message
		* @param exchange Switch
		* @param routingKey Routing key
		*/
		@Override
		public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
			System.out.println("Error code:"+replyCode);
			System.out.println("Message:"+message);
			System.out.println("Error message:"+replyText);
			System.out.println("Switch:"+exchange);
			System.out.println("Routing key:"+routingKey);
		}
	});
	rabbitTemplate.convertAndSend("spring_direct_exchange","return","test return");
}

2. How the consumer acknowledges receipt of the message

The above two methods ensure the failure processing of messages from the producer to the queue. Next, the mode of confirming the receipt of the message by the consumer can deal with the exception when the consumer processes the message.
Generally, when configuring the listener, if the acknowledge is not set to manual, it will automatically sign in by default. If the message is automatically signed in, once the message is received by the Consumer, it will automatically acknowledge the receipt and remove the corresponding message from the message cache of RabbitMQ.
However, in the actual business processing, it is likely that the message will be lost if an exception occurs in the business processing after the message is received. If manual validation is established, channel. is required after successful business processing. Basicack(), manually sign in. If there is an exception, call channel Basicnack () method to automatically resend the message. In channel In the basicnack () method, if the request is set to true, the message will be put back to the queue. If the request is not set to true, the message will be lost even if the nack is used to confirm the message.
When configuring the Listener in the configuration file, set acknowledge="manual":

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/rabbit
        http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">


    <!-- Create connection project -->
    <context:property-placeholder location="classpath:rabbitmq.yaml"/>
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}" port="${rabbitmq.port}"
                               username="${rabbitmq.username}" password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
    />
    <bean id="ackListener" class="com.wxf.listener.AckListener"/>
    <bean id="prefetchListener" class="com.wxf.listener.PrefetchListener"/>
    <rabbit:admin connection-factory="connectionFactory"/>
    <!-- Listen for messages through listeners -->
    <rabbit:listener-container connection-factory="connectionFactory" auto-declare="true" acknowledge="manual" prefetch="1">
        <rabbit:listener ref="prefetchListener" queue-names="spring_direct_queue_confirm" />
    </rabbit:listener-container>
</beans>

Create a listener, implement the ChannelAwareMessageListener interface, and override the onMessage method. Before message confirmation, add i=3/0 to simulate the exception generated during message processing. It will jump to the catch method, and then reject the message through basicNack. After rejecting the message, if you want to put the message back to the queue, set request = true, otherwise the message will be discarded.

@Component
public class AckListener implements ChannelAwareMessageListener {

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("deliveryTag: "+deliveryTag);
        try{
            System.out.println("Message content:"+message.getBody());
            //2. Processing business logic
            System.out.println("Processing business logic...");
            int i=3/0;
            //3. Manual sign in
            channel.basicAck(deliveryTag,true);
        }catch (Exception e){
            // Request is to put back the queue. If you don't set to put back the queue, the information will still be lost
            channel.basicNack(deliveryTag,true,true);
        }
    }
}

This can correspond to the GetMessage from the queue in the graphical interface as ack, that is, to confirm the receipt of the message and delete the message from the queue. nack is a preview message, and the message will not be deleted from the queue.

Topics: Java RabbitMQ Spring