RabbitMQ Core Knowledge Summary!

Posted by keigowei on Tue, 14 Sep 2021 21:18:08 +0200

This article has been included in the github repository, which is used to share Java related knowledge summary, including Java basics, MySQL, Spring Boot, MyBatis, Redis, RabbitMQ, computer network, data structure and algorithms, etc. Welcome to pr and star!

GitHub address:https://github.com/Tyson0314/Java-learning

If github is not accessible, you can access the gitee repository.

Gitee address:https://gitee.com/tysondai/Java-learning

Article Directory:

brief introduction

RabbitMQ is a message queue developed by erlang. Message queues are used for asynchronous collaboration between applications.

Basic concepts

Message: consists of a message header and a message body. The message body is opaque, while the message header consists of a series of optional attributes, including routing-key, priority, delivery-mode (whether persistent storage) and so on.

Publisher: The producer of the message.

Exchange: Receives messages and routes them to one or more Queues. default exchange is the default direct switch with an empty string name. Each new Queue is automatically bound to the default switch with the same routing key name as the Queue name.

Binding: Associate Exchange with Queue through Binding so that Exchange knows which Queue to route messages to.

Queue: Stores messages and queues are first in, first out. A message can be distributed to one or more queues.

Virtual host: Each Vhost is essentially a mini version of RabbitMQ server with its own queues, switches, bindings, and permissions mechanisms. Vhost is the basis of the AMQP concept and must be specified at connection time. RabbitMQ defaults to Vhost /. When multiple different users use the same RabbitMQWhen servers provide services, they can be divided into multiple vhosts, and each user creates exchange s and queues in his own vhost.

Broker: Message Queuing server entity.

When to use MQ

For operations that do not require immediate effect, they can be split up, executed asynchronously, and implemented using message queues.

For example, in a common order system, the business logic after a user clicks on an order button may include: deducting inventory, generating the corresponding document, sending a text message notification. In this scenario, you can use MQ. Place the text message notification into the MQ asynchronous execution, and the main process of the order (such as deducting inventory, generating the corresponding document)Send a message to MQ when finished, allowing the main process to finish quickly, while other threads consume MQ messages.

Advantages and disadvantages

Disadvantages: erlang implementation is not conducive to secondary development and maintenance; performance is worse than kafka, single-machine throughput of production and consumption messages is about 120,000 when persistent messages and ACK confirm, and single-machine throughput of Kafka is at 100,000 level.

Advantages: management interface, easy to use, high reliability, rich functionality, support for message persistence, message confirmation mechanism, multiple message distribution mechanisms.

Exchange type

Exchange distributes messages according to different distribution strategies for different types. There are currently four types: direct, fanout, topic, headers. The headers mode routes messages based on the headers of the message. In addition, the headers and direct switches are identical, but their performance is much worse.

Exchange rules.

Type NameType Description
fanoutRoute all messages sent to this Exchange to all Queue s bound to it
directRouting Key==Binding Key
topicFuzzy Matching
headersExchange does not rely on routing key and binding key matching rules to route messages, but instead matches based on the header attribute in the message content being sent.

direct

The direct switch routes messages to a queue where the binding key and routing key match exactly. It is a fully matched, unicast mode.

fanout

All messages sent to a fanout-type switch are routed to all queues bound to that switch. A fanout-type forwarding message is the fastest.

topic

topic switches use routing key s and binding keys for fuzzy matching, and successful matching sends messages to the corresponding queues. Roug keys and binding keys are periods'. 'delimited strings. There can be two special characters'*' and'#'in binding keys for fuzzy matching, where'*' matches one word and'#'matches multiple words.

headers

The headers switch is routed based on the headers property in the message content sent. A set of key-value pairs is specified when binding a Queue to Exchange; when a message is sent to Exchange, RabbitMQ takes the headers of the message (also in the form of a key-value pair)Compares whether the key-value pairs match exactly the key-value pairs specified when the Queue is bound to Exchange; if they match exactly, messages will be routed to the Queue; otherwise, they will not be routed to the Queue.

Message Loss

Message Loss Scenarios: Producer production message to RabbitMQ Server message is lost, RabbitMQ Server stored message is lost, and RabbitMQ Server to consumer message is lost.

Message loss can be solved in three ways: producer confirmation mechanism, consumer manual confirmation message and persistence.

Producer Confirmation Mechanism

The producer sends the message to the queue and cannot ensure that the message successfully reaches the server.

Solution:

  1. Transaction mechanism. Blocks the sender after a message has been sent and waits for a response from RabbitMQ before continuing to send the next message. Poor performance.
  2. Turn on the producer confirmation mechanism and RabbitMQ will send an ack to the producer as long as the message is successfully sent to the switch (even if the message is not received by Queue). If the message is not successfully sent to the switch, a nack message will be sent indicating that the send failed.

In Springboot, confirm mode is set through the publisher-confirms parameter:

spring:
    rabbitmq:   
        #Open confirm confirmation mechanism
        publisher-confirms: true

Provides a callback method on the production side. When the service side confirms one or more messages, the producer calls back this method, and subsequently processes the messages based on the specific results, such as resending, logging, and so on.

// Whether the message was successfully sent to Exchange
final RabbitTemplate.ConfirmCallback confirmCallback = (CorrelationData correlationData, boolean ack, String cause) -> {
            log.info("correlationData: " + correlationData);
            log.info("ack: " + ack);
            if(!ack) {
                log.info("exception handling....");
            }
    };

rabbitTemplate.setConfirmCallback(confirmCallback);

Routing Unreachable Messages

The producer validation mechanism only ensures that messages arrive at the switch correctly, and messages that fail to route from the switch to Queue are discarded, resulting in message loss.

There are two ways to handle non-routable messages: the Return message mechanism and the backup switch.

Return message mechanism

The Return message mechanism provides a callback function, ReturnCallback, which calls back messages when they fail to route from the switch to the Queue. mandatory needs to be set to true to listen for messages whose routes are unreachable.

spring:
    rabbitmq:
        #Triggering ReturnCallback must set mandatory=true, otherwise Exchange will discard the message if it does not find the Queue and will not trigger ReturnCallback
        template.mandatory: true

Listen for route unreachable messages through ReturnCallback.

    final RabbitTemplate.ReturnCallback returnCallback = (Message message, int replyCode, String replyText, String exchange, String routingKey) ->
            log.info("return exchange: " + exchange + ", routingKey: "
                    + routingKey + ", replyCode: " + replyCode + ", replyText: " + replyText);
rabbitTemplate.setReturnCallback(returnCallback);

When a message fails to route from the switch to Queue, it returns return exchange:, routingKey: MAIL, replyCode: 312, replyText: NO_ROUTE.

Backup switch

The backup switch alternate-exchange is a common exchange. When you send a message to the corresponding exchange, if it does not match a queue, it is automatically transferred to the queue corresponding to the backup switch so that the message is not lost.

Consumer Manual Message Confirmation

It is possible that the consumer will go down before they have time to process the MQ service, causing the message to be lost. Because the messenger defaults to automatic ack, once the consumer receives the message, they will notify MQ Server that the message has been processed, and MQ will remove the message.

Solution: The consumer is set to confirm the message manually. The consumer replies ack to the broker after processing the logic, indicating that the message has been successfully consumed and can be deleted from the broker. When the message consumer fails to consume, reply nack to the broker and decide whether to rejoin or remove from the broker or to enter the dead letter queue based on configuration. As long as the consumer is confiscatedAcknowledgment, the broker keeps this message, but it doesn't requeue or distribute it to other consumers.

Consumer set manual ack:

#Set up consumer manual ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual

When the message is processed, confirm manually:

    @RabbitListener(queues = RabbitMqConfig.MAIL_QUEUE)
    public void onMessage(Message message, Channel channel) throws IOException {

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        //Manual ack; the second parameter, multiple, is set to true, meaning that messages before the deliveryTag serial number (including itself) have been received, and false means that a message has been received
        channel.basicAck(deliveryTag, true);
        System.out.println("mail listener receive: " + new String(message.getBody()));
    }

When the message consumption fails, the consumer replies to the broker nack. If the consumer sets the requeue to false, the broker deletes the message or enters the dead-letter queue after the nack, otherwise the message is re-queued.

Persistence

If the RabbitMQ service is restarted unexpectedly, the message will be lost. RabbitMQ provides a persistence mechanism to persist messages in memory to the hard disk, even if RabbitMQ is restarted, the message will not be lost.

Message persistence requires the following:

  1. Message set persistence. Before publishing a message, set delivery mode to 2, indicating that the message needs to be persisted.
  2. Queue settings are persisted.
  3. Switch settings are persisted.

When a message is published to a switch, Rabbit writes the message to the persistence log before sending a response to the producer. Once a message is consumed from the queue and confirmed, RabbitMQ removes the message from the persistence log. Before consuming the message, if RabbitMQ restarts, the server automatically rebuilds the switch and queue to load persistenceEnsure that messages in the log are not lost by placing them in the appropriate queue or switch.

Mirrored Queues

When MQ fails, the service will be unavailable. A mirroring queue mechanism of RabbitMQ is introduced to mirror queue over other nodes in the cluster. If one node in the cluster fails, it can automatically switch to another node in the mirror to ensure service availability.

Usually each mirror queue contains one master and multiple slaves, corresponding to different nodes. All messages sent to the mirror queue are always sent directly to the master and all slaves. All actions except publish are sent to the master only, and then the master broadcasts the results of the commands to the slave. Consumer actions from the mirror queue are actually in the form ofExecuted on the master.

Reconsumption

There are two reasons for duplicate messages: 1. duplicate messages in production and 2. duplicate messages in consumption.

The producer sends a message to MQ, there is network fluctuation during MQ confirmation, the producer does not receive confirmation, then the producer sends this message again, causing MQ to receive duplicate messages.

After successful consumer consumption, there is network fluctuation when confirming to MQ. MQ does not receive confirmation. In order to ensure that the message is not lost, MQ will continue to deliver the previous message to the consumer. At this time, the consumer receives two identical messages. Because duplicate messages are caused by network reasons, it is unavoidable.

Solution: When sending messages, let each message carry a global unique ID. When consuming messages, first judge if the message has been consumed, and then guarantee the idempotency of message consumption logic. The specific consumption process is:

  1. Consumers query redis/db for the presence of a message based on id after getting it
  2. If it doesn't exist, then normal consumption, write redis/db after consumption is complete
  3. If it exists, it proves that the message has been consumed and discarded directly

Consumer End Limit

When the RabbitMQ server is backing up a large number of messages, messages in the queue will flood into the consumer side, possibly causing the consumer side server to burst. In this case, the consumer side needs to be restricted.

Spring RabbitMQ provides the parameter prefetch to set the number of messages processed by a single request. If the message processed by the consumer at the same time reaches the maximum value, the consumer will block and will not consume new messages until there is a message ack.

Turn on consumer current limiting:

#Number of messages processed in a single request, maximum number of unack s
spring.rabbitmq.listener.simple.prefetch=2

Native RabbitMQ also provides prefetchSize and global s, which Spring RabbitMQ does not have.

//Single message size limit, 0 means no limit
//global: Restrict whether the current limiting function is channel level or consumer level. When set to false, consumer level, the current limiting function takes effect and true does not have current limiting function, because channel level is not yet implemented.
void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException;

Dead Letter Queue

Queue where messages with consumption failure are stored.

Reasons for message consumption failure:

  • Message rejected and not requeued (requeue=false)
  • Message Timeout Not Consumed
  • Maximum queue length reached

Set exchange and queue for the dead letter queue, then bind:

	@Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange(RabbitMqConfig.DLX_EXCHANGE);
    }

    @Bean
    public Queue dlxQueue() {
        return new Queue(RabbitMqConfig.DLX_QUEUE, true);
    }

    @Bean
    public Binding bindingDeadExchange(Queue dlxQueue, DirectExchange deadExchange) {
        return BindingBuilder.bind(dlxQueue).to(deadExchange).with(RabbitMqConfig.DLX_QUEUE);
    }

Add two parameters to the normal queue to bind the normal queue to the dead letter queue. When message consumption fails, messages are routed to the dead letter queue.

    @Bean
    public Queue sendSmsQueue() {
        Map<String,Object> arguments = new HashMap<>(2);
        // Bind the queue to a private switch
        arguments.put("x-dead-letter-exchange", RabbitMqConfig.DLX_EXCHANGE);
        arguments.put("x-dead-letter-routing-key", RabbitMqConfig.DLX_QUEUE);
        return new Queue(RabbitMqConfig.MAIL_QUEUE, true, false, false, arguments);
    }

Producer full code:

@Component
@Slf4j
public class MQProducer {

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    RandomUtil randomUtil;

    @Autowired
    UserService userService;

    final RabbitTemplate.ConfirmCallback confirmCallback = (CorrelationData correlationData, boolean ack, String cause) -> {
            log.info("correlationData: " + correlationData);
            log.info("ack: " + ack);
            if(!ack) {
                log.info("exception handling....");
            }
    };


    final RabbitTemplate.ReturnCallback returnCallback = (Message message, int replyCode, String replyText, String exchange, String routingKey) ->
            log.info("return exchange: " + exchange + ", routingKey: "
                    + routingKey + ", replyCode: " + replyCode + ", replyText: " + replyText);

    public void sendMail(String mail) {
        //Appearing thread insecurity range 100000 - 999999
        Integer random = randomUtil.nextInt(100000, 999999);
        Map<String, String> map = new HashMap<>(2);
        String code = random.toString();
        map.put("mail", mail);
        map.put("code", code);

        MessageProperties mp = new MessageProperties();
        //Instead of using Message in a production environment, you use tools such as fastJson to convert objects to json format for sending
        Message msg = new Message("tyson".getBytes(), mp);
        msg.getMessageProperties().setExpiration("3000");
        //If the consumer side is set to a manual ACK, the production side must send the correlationData when sending the message, which is globally unique to uniquely identify the message.
        CorrelationData correlationData = new CorrelationData("1234567890"+new Date());

        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback(confirmCallback);
        rabbitTemplate.setReturnCallback(returnCallback);
        rabbitTemplate.convertAndSend(RabbitMqConfig.MAIL_QUEUE, msg, correlationData);

        //Save in redis
        userService.updateMailSendState(mail, code, MailConfig.MAIL_STATE_WAIT);
    }
}

Consumer Complete Code:

@Slf4j
@Component
public class DeadListener {

    @RabbitListener(queues = RabbitMqConfig.DLX_QUEUE)
    public void onMessage(Message message, Channel channel) throws IOException {

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        //Manual ack
        channel.basicAck(deliveryTag,false);
        System.out.println("receive--1: " + new String(message.getBody()));
    }
}

When there is dead letter in the normal queue, RabbitMQ will automatically republish this message to the set dead letter switch and route it to the dead letter queue. You can listen for messages in the dead letter queue and process them accordingly.

Other

pull mode

pull mode mainly obtains messages through channel.basicGet method, the sample code is as follows:

GetResponse response = channel.basicGet(QUEUE_NAME, false);
System.out.println(new String(response.getBody()));
channel.basicAck(response.getEnvelope().getDeliveryTag(),false);

Message expiration time

You can set an expiration time for messages in milliseconds (ms) when they are sent from the production side

Message msg = new Message("tyson".getBytes(), mp);
msg.getMessageProperties().setExpiration("3000");

You can also specify the ttl of the queue at the time of queue creation, starting from the time the message enters the queue, beyond which the message will be removed.

Reference Links

RabbitMQ Foundation

Springboot Integration RabbitMQ

Message Persistence for RabbitMQ

RabbitMQ Send Mail Code

Online rabbitmq problem

Finally, share a github warehouse with over 200 classic computer books, including C Language, C++, Java, Python, Front End, Database, Operating System, Computer Network, Data Structure and Algorithms, Machine Learning, Programming Life, etc. You can star t by searching directly on it next time, the warehouse is continuously updated~

GitHub address:https://github.com/Tyson0314/java-books

If github is not accessible, you can access the gitee repository.

Gitee address:https://gitee.com/tysondai/java-books

Topics: Java RabbitMQ Spring Boot