8

1. Why did your company choose RabbitMQ as its messaging middleware

When selecting the message queue, we investigated ActiveMQ, RabbitMQ, RocketMQ, and Kafka that are commonly used in the market.

  1. RabbitMQ is relatively mature and stable, which is the main reason we chose it.
  2. The community is relatively active and there are complete information for reference.
  3. The throughput of Rabbitmq can reach ten thousand, which fully meets the requirements of our system.
  4. RabbitMQ is developed in Erlang language and has better performance.
  5. There is a complete visual interface for easy viewing.

2. What are the advantages and disadvantages of message queues

The advantages are:

  • Asynchronous processing-Compared with traditional serial and parallel methods, it improves system throughput.
  • Application decoupling-communication between systems through messages, without worrying about the processing of other systems.
  • Traffic cutting-the amount of requests can be controlled by the length of the message queue; high concurrent requests in a short period of time can be alleviated.

The disadvantages are:

  • Reduced system availability
  • Increased system complexity

3. What are the commonly used working modes of RabbitMQ

2.1 Simple model

  • p: Producer
  • C: Consumer
  • Red part: queue, message queue

2.2 Working model

In this mode, a message can only be consumed by one consumer. By default, each consumer is polled for consumption.

  • p: Producer
  • C1, C2: Consumer
  • Red part: queue, message queue

2.3 Publish and Subscribe Model (fanout)

In this model, all consumers can consume messages sent by producers.

  • p: Producer
  • X: Switch
  • C1, C2: Consumer
  • Red part: queue, message queue

2.4 routing model (routing)

This model of messages sent by consumers, different types of messages can be consumed by different consumers.

  • p: Producer
  • X: Exchange, after receiving the message from the producer, it will deliver the message to the queue that exactly matches the routing key
  • C1, C2: Consumer
  • Red part: queue, message queue

2.5 topic model (topic)

This model, like the direct model, can route messages to different queues based on the routing key, but this model allows the queue to use wildcards when binding the routing key. This type of routing key is composed of one or more words, and multiple words are divided .

Introduction to wildcards:

* : Only match one word

# : match one or more words

4. How to ensure that the message is not lost (how to ensure the reliability of the message)

A message goes through three stages from production to consumption, namely the producer, the MQ and the consumer. For RabbitMQ, the transfer of the message also involves the switch. Therefore, there are four cases of message loss in RabbitMQ

Respectively are

  1. The message producer did not successfully send the message to MQ, resulting in the loss of the message
  2. The switch is not routed to the message queue, causing the message to be lost
  3. When the message is in MQ, the MQ is down and the message is lost
  4. An exception occurred when the consumer was consuming the message, causing the message to be lost

For the four situations mentioned above, deal with them separately

  1. The amqp protocol provides a transaction mechanism. The transaction is opened when the message is delivered. If the message delivery fails, the transaction is rolled back. Few people use the transaction. In addition to transactions, RabbitMQ also provides a producer confirm mechanism (publisher confirm). The producer sets the channel to confirm (confirm) mode. Once the channel enters the confirm mode, all messages published on the channel will be assigned a unique ID (starting from 1). Once the message is delivered to all matching queues, RabbitMQ will send an acknowledgment (Basic.Ack) to the producer (containing the unique ID of the message), which makes the producer know that the message has arrived at the destination correctly.
# 开启生产者确认机制,
# 注意这里确认的是是否到达交换机
spring.rabbitmq.publisher-confirm-type=correlated
@RestController
public class Producer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("send")
    public void sendMessage(){
        /**
         * 生产者确认消息
         */
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println(correlationData);
                System.out.println(ack);
                System.out.println(cause);
            }
        });
        rabbitTemplate.convertAndSend("s","error","这是一条错误日志!!!");
    }
}
  1. Return this message to the producer when the message fails to match the queue from the exchange
spring.rabbitmq.publisher-returns=true
@RestController
public class Producer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("send")
    public void sendMessage(){
        /**
         * 消息未达队列时返回该条消息
         */
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage) {
                System.out.println(returnedMessage);
            }
        });
        rabbitTemplate.convertAndSend("s","error","这是一条错误日志!!!");
    }
}
  1. If the message is lost in the switch or queue, we only need to persist the switch and queue.
/**
  * 定义一个持久化的topic交换机
  * durable 持久化
  * @return
 */
@Bean
public Exchange exchangeJavatrip(){
    return ExchangeBuilder.topicExchange(EXCHANGE).durable(true).build();
}

/**
 * 定义一个持久化的队列
 * durable 持久化
 * @return
 */
@Bean
public Queue queueJavatrip(){
    return QueueBuilder.durable(QUEUE).build();
}
  1. The consumer opens the manual sign-off mode, and confirms the ack after the consumption is completed.
spring.rabbitmq.listener.simple.acknowledge-mode=manual
@RabbitListener(queues = MqConfig.QUEUE)
public void receive(String body, Message message, Channel channel) throws Exception{
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    System.out.println(deliveryTag);
    // 系统业务逻辑判断是否签收
    if(deliveryTag % 2 == 0){
        channel.basicAck(deliveryTag,false);
    }else{
        // 第二个参数是否批量确认,第三个参数是否重新回队列
        channel.basicNack(deliveryTag,false,true);
    }
}

5. How to ensure that the message is not re-consumed (how to ensure the idempotence of the message)

There are two reasons for repeated messages:

  1. Duplicate messages during production

    Since the producer sends a message to MQ, network fluctuations occur when MQ confirms, and the producer does not receive the confirmation. In fact, MQ has already received the message. At this time, the producer will resend this message.

  2. Messages are repeated during consumption.

    After the consumer succeeded in consumption, network fluctuations occurred when MQ was confirmed, and MQ did not receive the confirmation. In order to ensure that the message is consumed, MQ will continue to deliver the previous message to the consumer. At this time, the consumer received two identical messages.

Since message duplication is caused by network fluctuations and other reasons, it is unavoidable. What we can do is to ensure the idempotence of messages to prevent repeated business processing. The specific treatment plan is:

Let each message carry a globally unique ID to ensure the idempotence of the message. The specific consumption process is:

  1. After receiving the message, the consumer first queries whether the message exists in redis/db according to the id.
  2. If it does not exist, it will be consumed normally and written to redis/db after consumption.
  3. If it exists, it proves that the message has been consumed and discarded directly.
@RabbitListener(queues = MqConfig.QUEUE)
public void receive(Message message, Channel channel){

    String messageId = message.getMessageProperties().getMessageId();
    String body = new String(message.getBody());
    String redisId = redisTemplate.opsForValue().get(messageId)+"";
    // 如果redis中存有当前消息的消息id
    // 则证明消费过
    if(messageId.equals(redisId)){
        return;
    }
    redisTemplate.opsForValue().set(messageId, UUID.randomUUID());
}

6. How to deal with a large amount of messages

There are two reasons for the accumulation of messages

  1. Network failure, consumers cannot consume normally
  2. The consumer did not confirm the ack after consumption

The solution is as follows:

  1. Check and repair consumer failures, so that they can consume normally
  2. Write a temporary program to send the accumulated messages to the MQ cluster with larger capacity to increase the rapid consumption of consumers
  3. After the accumulated messages are consumed, stop the temporary program and resume normal consumption

7. What is a dead letter? How to deal with dead letters

When a message appears in the following three situations in the queue, the message will become a dead letter.

  • The message was rejected (basic.reject / basic.nack), and requeue = false
  • Message TTL expired
  • The queue reaches the maximum length

When a message becomes a dead letter in a queue, if a dead letter queue is configured, it will be republished to the dead letter exchange, and the dead letter exchange will deliver the dead letter to a queue, which is the dead letter queue.

After a message becomes a dead letter, it will generally be stored in the dead letter queue, and then the dead letter in the library will be reposted to the message queue at regular intervals.

8. If I have an order and I have not paid within 30 minutes, I will close the order. How to use RabbitMQ to achieve it

RabbitMQ can use a dead letter queue to achieve delayed consumption. After the user places an order, the order information is delivered to the message queue, and the message expiration time is set to 30 minutes. If the user pays, the order is normally closed. If the user does not pay and the message reaches the expiration time, the message will enter the dead letter exchange, and the consumer will consume the dead letter queue to close the order.

9. How does RabbitMQ ensure high availability

RabbitMQ has two cluster modes, namely a normal cluster and a mirrored cluster. The normal mode cannot guarantee the high availability of RabbitMQ.

Ordinary cluster

If there are three nodes, rabbitmq1, rabbitmq2, rabbitmq3, the message actually only exists in one of the nodes, and the three nodes only have the same metadata, that is, the structure of the queue. When the message enters the queue of the rabbitmq2 node, the consumer will start from the rabbitmq1 node. For consumption, rabbitmq1 and rabbitmq2 will communicate temporarily, get the message from rabbitmq2 and return it to the consumer.

There are two problems with this model:

  1. When rabbitmq2 goes down, the message cannot be consumed normally, and it is not truly highly available.
  2. The actual data is still on a single instance, there is a bottleneck problem

Mirror cluster

If there are three nodes, rabbitmq1, rabbitmq2, and rabbitmq3, each instance can communicate with each other. Every time a producer writes a message to the queue, each rabbitmq node has queue message data and metadata. This mode is used in scenarios with high reliability requirements.

Pay attention, don't get lost

If you think the article is good, welcome to pay attention, like, and collect. Your support is the driving force for my creation. Thank you all.

If there is a problem with the writing of the article, please don't hesitate to write, welcome to leave a message and point it out, and I will check and revise it in time.

If you want to see more things, you can search for "Java Journey" on WeChat to follow. Reply to the "Manual" to receive the Java Interview Manual!


Java旅途
1.1k 声望6.1k 粉丝