Springboot + rabbitmq uses the message confirmation mechanism and feels like it’s in a hole

Time:2020-10-22

This article is included in personal blog: www.chengxy-nds.top Sharing technology resources and making progress together

Recently, the Department has called on everyone to organize more technology sharing meetings, saying that it is necessary to activate the technical atmosphere of the company. But I have seen through everything for a long time. I know that this TM is just for brushingKPI。 But then again, it’s a good thing. Instead of holding those boring wrangles, more technical exchanges are still conducive to personal growth.

So I volunteered to participate in the sharing, cough cough ~, really not for that pointKPII want to learn with you!

Springboot + rabbitmq uses the message confirmation mechanism and feels like it's in a hole

What I’m sharing this time isspringboot + rabbitmqHow to realize the message confirmation mechanism, as well as a little experience in the actual development, the overall content is relatively simple. Sometimes things are so magical, and the simpler things are, the more likely they are to make mistakes.

You can see the useRabbitMQIn the future, our service links have obviously become longer. Although the decoupling between systems has been achieved, the scenarios that may cause message loss have also increased. For example:

  • Message producer – > rabbitmq server (message sending failed)

  • Rabbitmq server’s own failure causes message loss

  • Message consumer > rabbitmq service (consumption message failed)

Springboot + rabbitmq uses the message confirmation mechanism and feels like it's in a hole

Therefore, if you can not use middleware, try not to use it. If you use it for the sake of use, it will only add to your worries. After the message confirmation mechanism is turned on, although the accurate delivery of messages is ensured to a great extent, due to the frequent confirmation interaction,rabbitmqAs a result, the overall efficiency becomes low, and the throughput decreases seriously. For messages that are not very important, it is not recommended that you use the message confirmation mechanism.


Let’s do it firstspringboot + rabbitmqMessage confirmation mechanism, and then make a specific analysis of the problems encountered.

1、 Prepare the environment

1. Introducing rabbitmq dependency package

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

2. Modification application.properties to configure

It needs to be enabled in the configurationSenderandConsumer sideMessage confirmation for.

spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest.rabbitmq.publisher-confirms=true#Sender onreturnConfirmation mechanism.rabbitmq.publisher-returns=true####################################################.rabbitmq.listener.simple.acknowledge-mode=manual.rabbitmq.listener.simple.retry.enabled=true

3. Define exchange and queue

Defining switchesconfirmTestExchangeAnd queueconfirm_test_queueAnd bind the queue to the switch.

@Configuration
public class QueueConfig {

    @Bean(name = "confirmTestQueue")
    public Queue confirmTestQueue() {
        return new Queue("confirm_test_queue", true, false, false);
    }

    @Bean(name = "confirmTestExchange")
    public FanoutExchange confirmTestExchange() {
        return new FanoutExchange("confirmTestExchange");
    }

    @Bean
    public Binding confirmTestFanoutExchangeAndQueue(
            @Qualifier("confirmTestExchange") FanoutExchange confirmTestExchange,
            @Qualifier("confirmTestQueue") Queue confirmTestQueue) {
        return BindingBuilder.bind(confirmTestQueue).to(confirmTestExchange);
    }
}

rabbitmqThe message confirmation is divided into two parts: sending message confirmation and message receiving confirmation.

Springboot + rabbitmq uses the message confirmation mechanism and feels like it's in a hole

2、 Message sending confirmation

Send message confirmation: used to confirm the producerproducerSend message tobrokerbrokerSwitch onexchangePost to queuequeueWhether the message is delivered successfully.

Message fromproducerreachrabbitmq brokerThere is oneconfirmCallbackConfirmation mode.

Message fromexchangereachqueueThere was one delivery failurereturnCallbackReturn mode.

We can use these twoCallbackTo ensure 100% delivery.

1. Confirmcallback confirmation mode

As long as the news israbbitmq brokerIt will trigger when it is receivedconfirmCallbackCallback.

@Slf4j
@Component
public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback {

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {

        if (!ack) {
            log.error("Abnormal message sending!");
        } else {
            log.info("Sender dad has received confirmation, correlationdata = {}, ACK = {}, cause = {}", correlationData.getId(), ack, cause);
        }
    }
}

Implementation interfaceConfirmCallback, rewrite itsconfirm()Method, which has three parameterscorrelationDataackcause

  • correlationData: there is only one internal objectidProperty to represent the uniqueness of the current message.
  • ack: message delivered tobrokerThe state of,trueIt means success.
  • cause: indicates the reason for the post failure.

But the news wasbrokerReceiving can only indicate that the message has arrived at the MQ server, and it does not guarantee that the message will be delivered to the destinationqueueInside. So we need to use it nextreturnCallback

2. Return callback mode

If the message is not delivered to the destinationqueueThe callback will be triggered inreturnCallbackOncequeueIf the message is not delivered successfully, the detailed delivery data of the current message will be recorded here to facilitate subsequent operations such as retransmission or compensation.

@Slf4j
@Component
public class ReturnCallbackService implements RabbitTemplate.ReturnCallback {

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.info("returnedMessage ===> replyCode={} ,replyText={} ,exchange={} ,routingKey={}", replyCode, replyText, exchange, routingKey);
    }
}

Implementation interfaceReturnCallback, rewritereturnedMessage()Method, which has five parametersmessage(message body)replyCode(response code)replyText(response content)exchange(switch)routingKey(queue).

The following is the specific message sendingrabbitTemplateSet inConfirmandReturnCallback, we passsetDeliveryMode()The message is persisted to create aCorrelationDataAdd an objectidby10000000000

@Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ConfirmCallbackService confirmCallbackService;

    @Autowired
    private ReturnCallbackService returnCallbackService;

    public void sendMessage(String exchange, String routingKey, Object msg) {

        * *
        rabbitTemplate.setMandatory(true);

        * *
        rabbitTemplate.setConfirmCallback(confirmCallbackService);

        * *
        rabbitTemplate.setReturnCallback(returnCallbackService);

        * *
        rabbitTemplate.convertAndSend(exchange, routingKey, msg,
                message -> {
                    message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                    return message;
                },
                new CorrelationData(UUID.randomUUID().toString()));
    }

3、 Message receiving acknowledgement

Message acknowledgement is a little easier than message send acknowledgement because there is only one message receipt(ack)The process. use@RabbitHandlerThe method of annotation should be addedchannel(channel)messageTwo parameters.

@Slf4j
@Component
@RabbitListener(queues = "confirm_test_queue")
public class ReceiverMessage1 {

    @RabbitHandler
    public void processHandler(String msg, Channel channel, Message message) throws IOException {

        try {
            log.info("Xiaofu received a message: {}", msg);

            //Todo specific business

            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

        }  catch (Exception e) {

            if (message.getMessageProperties().getRedelivered()) {

                log.error("Message failed to be processed repeatedly, refused to receive again...");

                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); //Reject message
            } else {

                log.error(Message is about to return to queue again);

                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); 
            }
        }
    }
}

There are three receipt methods for consumption message. Let’s analyze the meaning of each method.

1、basicAck

basicAck: indicates successful confirmation. After using this receipt method, the message will berabbitmq brokerDelete.

void basicAck(long deliveryTag, boolean multiple) 

deliveryTag: represents the message delivery sequence number. After each consumption message or message re delivery,deliveryTagWill increase. In manual message confirmation mode, we can specifydeliveryTagThe message ofacknackrejectAnd so on.

multiple: batch confirmation or not, the value istrueIt will be one-timeackAll messages less than currentdeliveryTagNews of.

Take a chestnutSuppose I send three messages firstdeliveryTagThey are 5, 6 and 7, but they are not confirmed. When I send the fourth message, at this timedeliveryTag8,multipleIf set to true, all messages of 5, 6, 7 and 8 will be confirmed.

2、basicNack

basicNack: indicates failure confirmation. Generally, this method is used when the message service is abnormal. The message can be redelivered to the queue.

void basicNack(long deliveryTag, boolean multiple, boolean requeue)

deliveryTag: indicates the message delivery sequence number.

multiple: batch confirmation or not.

requeue: value istrueThe message will be re queued.

3、basicReject

basicReject: reject message, andbasicNackThe difference is that batch operations are not possible, and other usages are very similar.

void basicReject(long deliveryTag, boolean requeue)

deliveryTag: indicates the message delivery sequence number.

requeue: value istrueThe message will be re queued.

4、 Testing

Send a message to test whether the message confirmation mechanism is effective. From the execution result, the sender successfully callback after sending the message, and the consumer successfully consumes the message.

Springboot + rabbitmq uses the message confirmation mechanism and feels like it's in a hole

Use the bag grabbing toolWiresharkTake a lookrabbitmqAMQP protocol interaction has changed a lotackThe process.

Springboot + rabbitmq uses the message confirmation mechanism and feels like it's in a hole

5、 Step on the pit log

1. No message confirmation

It’s a very unskilled pit, but it’s very easy to make mistakes.

Open the message confirmation mechanism, and don’t forget to consume informationchannel.basicAckOtherwise, the message will always exist, resulting in repeated consumption.

Springboot + rabbitmq uses the message confirmation mechanism and feels like it's in a hole

2. Unlimited delivery of messages

When I first came into contact with the message confirmation mechanism, the consumer code was written as follows. The idea is simple: confirm the message after processing the business logic,int a = 1 / 0Put the message back on the queue after an exception occurs.

@RabbitHandler
    public void processHandler(String msg, Channel channel, Message message) throws IOException {

        try {
            log.info("Consumer 2 received: {}", msg);

            int a = 1 / 0;

            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

        } catch (Exception e) {

            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        }
    }

But there is a problem, once the business code appearsbug99.9% of the cases will not be automatically repaired. A message will be infinitely posted to the queue, and the consumer will execute infinitely, resulting in an endless loop.

Springboot + rabbitmq uses the message confirmation mechanism and feels like it's in a hole

LocalCPUI was filled in an instant. You can imagine how flustered I was at that time when the production environment caused the service to crash.

Springboot + rabbitmq uses the message confirmation mechanism and feels like it's in a hole

andrabbitmq managementThere is only one unconfirmed message.

Springboot + rabbitmq uses the message confirmation mechanism and feels like it's in a hole

After testing and analysis, it is found that when the message is re delivered to the message queue, the message will not return to the end of the queue, but still at the head of the queue.

The consumer will immediately consume the message, the business process will throw an exception, and the message will rejoin the queue, and so on. As a result, message queuing processing is blocked and normal messages cannot run.

Our solution at that time was to answer the message first. At this time, the message queue would delete the message. At the same time, we would send the message to the message queue again, and the abnormal message would be placed at the end of the message queue, so as to ensure that the message will not be lost and the normal business can be carried out.

channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//Resend message to end of team
channel.basicPublish(message.getMessageProperties().getReceivedExchange(),
                    message.getMessageProperties().getReceivedRoutingKey(), MessageProperties.PERSISTENT_TEXT_PLAIN,
                    JSON.toJSONBytes(msg));

However, this method does not solve the fundamental problem, and the error message will not be reported in time. After the number of message retries is set in the later optimization, when the maximum number of retries is reached, the queue will manually confirm the deletion of the message and persist the message intoMySQLAnd push the alarm for manual processing and timing task compensation.

3. Repeated consumption

How to ensure that the consumption of MQ is idempotent depends on the specific businessMySQL, orredisThe message is persisted and checked by the uniqueness property in the message.

demoOfGitHubAddress github.com/chengxy -nds/Springboot-…


Original is not easy, burning hair output content, if there is a loss of harvest, like to encourage it!

Hundreds of technical e-books have been sorted out and sent to our friends. Attention to public name reply【666】Collect it by yourself. We have set up a technology exchange group with some partners to discuss technology and share technical information, aiming to learn and progress together. If you are interested, please scan the code and join us!

Springboot + rabbitmq uses the message confirmation mechanism and feels like it's in a hole