RabbitMQ - message queue - upper part

Get to know MQ

What is MQ

MQ(message queue), in its literal sense, is essentially a queue. FIFO is first in, first out, but the contents stored in the queue are messages. It is also a cross process communication mechanism for upstream and downstream message delivery. In the Internet architecture, MQ is a very common upstream and downstream "logical decoupling + physical decoupling" message communication service. After using MQ, the upstream of message sending only needs to rely on MQ without relying on other services.

Why MQ

1. Flow peak elimination

For example, if the order system can process 10000 orders at most, this processing capacity is more than enough to deal with orders in normal periods
In the normal period, we can return the results one second after placing an order. However, in the peak period, if 20000 orders are placed, the operating system can't handle them and can only limit them
Users are not allowed to place orders after the number of orders exceeds 10000. Using message queue as buffer, we can cancel this limit and divide the orders placed in one second
Scattered for a period of time. At this time, some users may not receive the successful operation of placing an order until more than ten seconds after placing an order, but it is more difficult than those who cannot place an order
The test is better.

2. Application decoupling

Taking e-commerce applications as an example, there are order system, inventory system, logistics system and payment system. After the user creates the order, if the coupling
Call the inventory system, logistics system and payment system. If any subsystem fails, the order operation will be abnormal. When transformed into based
After using message queuing, the problems of inter system calls will be reduced a lot. For example, the logistics system needs a few minutes to repair due to failure. stay
In these few minutes, the memory to be processed by the logistics system is cached in the message queue, and the user's order can be completed normally. When logistics
After the system is restored, you can continue to process the order information. Medium order users can't feel the failure of the logistics system, so as to improve the availability of the system.

3. Asynchronous processing

Some inter service calls are asynchronous. For example, a calls B, and B takes a long time to execute, but a needs to know when B can complete execution. In the past, there were generally two ways. A calls B's query api for a period of time. Or A provides a callback api. After B is executed, api is called to notify the A service. Neither of these two methods is very elegant. Using the message bus can easily solve this problem. After a calls the B service, a only needs to listen to the message processed by B. when B processing is completed, it will send a message to MQ, which will forward the message to the a service. In this way, service a does not need to call B's query api or provide callback api. Similarly, service B does not need to do these operations. Service a can also get the message of successful asynchronous processing in time.

Classification of MQ

1.ActiveMQ

Advantages: single machine throughput of 10000 class, timeliness of ms class, high availability, high availability based on master-slave architecture, low message reliability and low probability of data loss

Disadvantages: the official community now maintains ActiveMQ 5.x less and less, and uses less in high-throughput scenarios.

2.Kafka

Kafka, a message middleware for big data, is the killer of big data. When it comes to message transmission in the field of big data, Kafka cannot be bypassed,
It is famous for its million level TPS throughput, and quickly becomes a favorite in the field of big data. It plays an important role in the process of data acquisition, transmission and storage
Plays an important role. It has been adopted by LinkedIn, Uber, Twitter, Netflix and other large companies.

Advantages: excellent performance, single machine write TPS is about one million pieces / s, and the biggest advantage is high throughput. Timeliness ms level availability non
Chang Gao, kafka is distributed. One data has multiple copies, and a few machines go down. It will not lose data and lead to unavailability. Consumers will adopt
The message is obtained by Pull, and the message is orderly. Through control, it can ensure that all messages are consumed and only consumed once; Excellent third party
Kafka Web management interface Kafka manager; It is mature in the log field and is used by many companies and open source projects; Functional support:
The function is relatively simple. It mainly supports simple MQ functions. Real time calculation and log collection in the field of big data are used on a large scale

Disadvantages: the Kafka single machine has more than 64 queues / partitions, and the load will soar obviously. The more queues, the higher the load, and the transmission will be cancelled
The message response time becomes longer, and the short polling method is used. The real-time performance depends on the polling interval. Consumption failure does not support retry; Support message sequence,
However, when an agent goes down, it will cause message disorder and slow community update;

3.RocketMQ

RocketMQ is an open-source product from Alibaba and implemented in Java language. It refers to Kafka and makes its own design
Some improvements. It is widely used by Alibaba in order, transaction, recharge, stream computing, message push, log stream processing, binglog distribution, etc
View.

Advantages: single machine throughput is 100000, availability is very high, distributed architecture, message loss can be achieved, MQ function is relatively perfect, or points
It is distributed and has good scalability. It supports 1 billion level message accumulation and will not cause performance degradation due to accumulation. The source code is java and we can read it ourselves
Read the source code and customize your company's MQ

Disadvantages: there are not many supported client languages. At present, java and c + +, of which c + + is immature; Community activity is average, not in MQ
To implement JMS and other interfaces in the core, some systems need to modify a lot of code to migrate

4.RabbitMQ

Released in 2007, it is a reusable enterprise message system based on AMQP (Advanced message queuing protocol), which is the most popular at present
One of the mainstream message oriented middleware.

Advantages: due to the high concurrency of erlang language, the performance is good; With a throughput of 10000, MQ functions are relatively complete, robust, stable and easy to use
Use, cross platform and support multiple languages, such as Python, Ruby,. NET, Java, JMS, C, PHP, ActionScript, XMPP and STOMP
Etc., supporting AJAX with complete documents; The management interface provided by open source is very good, easy to use, and the community is highly active; The update frequency is quite high

RabbitMQ official website

Disadvantages: the commercial version needs to be charged, and the learning cost is high

MQ selection

1.Kafka

The main feature of Kafka is that it processes message consumption based on Pull mode and pursues high throughput. Its initial purpose is to collect logs
And transmission, which is suitable for the data collection business of Internet services that generate a large amount of data. Large companies suggest that it can be selected. If there is log collection function,
Must be the first choice kafka.

2.RocketMQ

Born in the field of financial Internet and demanding high reliability, especially the order deduction and business cutting in e-commerce
Peak, when a large number of transactions pour in, the back end may not be able to handle it in time. RoketMQ may be more reliable in terms of stability. These services
The scenario has been tested many times in Alibaba double 11. If your business has the above concurrent scenarios, it is recommended to choose RocketMQ.

3.RabbitMQ

Combined with the concurrency advantages of erlang language, it has good performance, timeliness, microsecond level, high community activity, and the management interface is very easy to use
Convenient. If your data volume is not so large, small and medium-sized companies give priority to RabbitMQ with complete functions

RabbitMQ

RabbitMQ concept

RabbitMQ is a message oriented middleware: it accepts and forwards messages. You can use it as a courier site when you want to send a package
When wrapping, you put your package in the express station, and the courier will eventually send your express to the recipient. According to this logic, RabbitMQ is
A courier station, a courier to help you deliver express. The main difference between RabbitMQ and express station is that it does not process express mail, but receives it,
Store and forward message data

Four core concepts

producer

The program that generates the data sending message is the producer

Switch

Switch is a very important part of RabbitMQ. On the one hand, it receives messages from producers, on the other hand, it sends messages
Push to queue. The switch must know exactly how to handle the messages it receives, whether to push them to a specific queue or push them
Sending to multiple queues or discarding messages depends on the switch type

queue

Queue is a data structure used internally by RabbitMQ. Although messages flow through RabbitMQ and applications, they can only be stored
Stored in the queue. The queue is only constrained by the memory and disk limitations of the host. It is essentially a large message buffer. Many producers can
To send messages to a queue, many consumers can try to receive data from a queue. This is how we use queues

consumer

Consumption and reception have similar meanings. Most of the time, consumers are a program waiting to receive messages. Please note that producers, consumers
Most of the time, the and message oriented middleware are not on the same machine. The same application can be both a producer and a consumer.

RabbitMQ core

Six core modes: simple mode, work queue mode, publish subscribe mode, routing mode, topic mode and publish confirmation mode

Introduction to each noun

  • Broker: an application that receives and distributes messages. RabbitMQ Server is the Message Broker
  • Virtual host: designed for multi tenancy and security factors, the basic components of AMQP are divided into a virtual group, which is similar to the concept of namespace in the network. When multiple different users use the services provided by the same RabbitMQ server, they can be divided into multiple vhosts. Each user creates an exchange / queue in its own vhost
  • Connection: TCP connection channel between publisher / consumer and broker: if a connection is established every time RabbitMQ is accessed, the overhead of establishing TCP connection will be huge and the efficiency will be low when the message volume is large. A channel is a logical connection established within a connection. If the application supports multithreading, each thread usually creates a separate channel for communication. The AMQP method contains a channel id to help the client and message broker identify the channel, so the channels are completely separated. As a lightweight connection, channel greatly reduces the overhead of establishing TCP connection by the operating system
  • Exchange: message arrives at the first stop of the broker, matches the routing key in the query table according to the distribution rules, and distributes the message to the queue. Common types are: direct (point-to-point), topic (publish subscribe) and fan out (multicast)
  • Queue: the message is finally sent here to wait for the consumer to pick it up
  • Binding: the virtual connection between exchange and queue. The binding can contain routing key s. The binding information is saved in the query table in exchange for the distribution basis of message s

RabbitMQ installation

1. Official website address

https://www.rabbitmq.com/download.html

2. File upload

Upload to / usr/local/RabbitMQ /

3. Installation documents (installed in the following order)

rpm -ivh erlang-21.3-1.el7.x86_64.rpm
yum install socat -y
rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm

4. Common commands (executed in the following order)

Add boot start RabbitMQ service

chkconfig rabbitmq-server on

Start service

/sbin/service rabbitmq-server start

View service status

/sbin/service rabbitmq-server status

Stop service (select execute)

/sbin/service rabbitmq-server stop

Start the web management plug-in. Remember to stop the RbMQ service before starting it

rabbitmq-plugins enable rabbitmq_management

After the plug-in installation is completed, start RbMQ again

/sbin/service rabbitmq-server start

Restart the rabbitmq service, and then enter 192.168.163.128(Linux ip address): 15672 on the windows client. You need to open the port number and enter smoothly!

# Turn on the firewall
firewall-cmd --permanent --add-port=15672/tcp

# Restart effective
firewall-cmd --reload

Use the default account password (guest) to access the address http://192.168.112.128:15672/ There is a permission problem

4. Add a new user

Create account, user name and password

rabbitmqctl add_user admin 123

Set user role, super administrator

rabbitmqctl set_user_tags admin administrator

Set user permissions

set_permissions [-p <vhostpath>] <user> <conf> <write> <read>

rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"

User user_admin has the configuration, write and read permissions of all resources in the virtual host / vhost1

View current users and roles

rabbitmqctl list_users

5. Log in again with the admin user

6. Reset command

The command to close the application is

rabbitmqctl stop_app

The command to clear is

rabbitmqctl reset

The restart command is

rabbitmqctl start_app

Simple queue

We will write two programs in Java. The producer who sends a single message and the consumer who receives and prints the message. We will cover some details in the Java API.

In the figure below, "P" is our producer and "C" is our consumer. The middle box is a queue - RabbitMQ represents the message buffer reserved by the consumer

POM file dependency

    <!--appoint jdk Compiled version-->
    <build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>8</source>
                <target>8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

    <dependencies>
    <!--rabbitmq Dependent client-->
    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.8.0</version>
    </dependency>
    <!--A dependency on the operation file stream-->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.6</version>
    </dependency>
</dependencies>

Producer code

//Producer, send message
public class Producer
{
    //Queue name
    public static  final  String QUEUE_NAME= "hello";

    //Send a message
    public static void main(String[] args) throws IOException, TimeoutException {
        //Create a connection factory
        ConnectionFactory factory=new ConnectionFactory();
        //The factory connects to the queue of RabbitMQ through the IP of RqbbitMQ
        factory.setHost("192.168.112.128");
        //Set user name
        factory.setUsername("admin");
        //Set password
        factory.setPassword("123");

        //Create connection
        Connection connection = factory.newConnection();
        //Acquire channel
        Channel channel = connection.createChannel();
        //Generate a queue
        //Parameter 1: queue name
        //Parameter 2: whether the messages in the queue are persistent. By default, the messages are stored in memory
        //Parameter 3: whether the queue is only used by one message maker, that is, whether to share messages. true can be used by multiple consumers, and false can only be used by one consumer
        //Parameter 4: automatically delete. After the last consumer disconnects, whether the queue is automatically deleted. true: automatically delete false: not automatically delete
        //Parameter none: other parameters (delayed message...)
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //Send a message
        String message="hello world";
        //Send a message
        //Parameter 1: to which switch
        //Parameter 2: what is the key value of the route? This is the name of the queue
        //Parameter 3: other parameter information
        //Parameter 4: message body of sending message
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes(StandardCharsets.UTF_8));
        System.out.println("Message sent");
    }
}

Note: if connection error is reported, it is considered that the port number is not open. For the connection service, the requested port number is 5672, while for the visualizer service, the requested port number is 15672. Therefore, you need to open two ports 5672 and 15672. The test connection is successful!

Consumer code

//Consumer, receive message
public class Consumer
{
    //The name of the queue
    public static  final String QUEUE_NAME="hello";

    //receive messages
    public static void main(String[] args) throws IOException, TimeoutException {
        //Create a connection factory and return the newly created connection
        Connection connection = getConnection();
        //Create channel
        Channel channel = connection.createChannel();
        //Consumer News
        //Parameter 1: which queue to consume
        //Parameter 2: whether to respond automatically after successful consumption. true means automatic response, false means manual response
        //Parameter 3: callback during message delivery
        //Parameter 4: callback of consumer canceling consumption
        channel.basicConsume(QUEUE_NAME,true,
                //deliverCallback
                (consumerTag,message)->{
            //The message itself has a message header, message attributes and message body. Here we only need to get the message body
            System.out.println(new String(message.getBody()));
        },
                //cancelCallback
                consumerTag->{
            System.out.println("Message interrupted");
        });
    }

    private static Connection getConnection() throws IOException, TimeoutException {
        //Create connection factory
        ConnectionFactory factory=new ConnectionFactory();
        factory.setHost("192.168.112.128");
        factory.setPassword("123");
        factory.setUsername("admin");
        return factory.newConnection();
    }
}

Work queue

The main idea of work queue (also known as task queue) is to avoid executing resource intensive tasks immediately and having to wait for them to complete. Instead, we arranged for the task to be carried out later. We encapsulate the task as a message and send it to the queue. The worker process running in the background will pop up the task and finally execute the job. When there are multiple worker threads, these worker threads will handle these tasks together.

Rotation training distribution message

In this case, we will start two worker threads, a message sending thread. Let's see how their two worker threads work.

Extraction tool class

/*
 * This class is a tool class for creating channels for connection factories
 * */
public enum RabbitMqUtils
{
    //ctrl+shift+u becomes uppercase
    INSTANCE;

    public static RabbitMqUtils getInstance()
    {
        return INSTANCE;
    }
    
        // Get a connected channel
        public Channel getChannel() throws IOException, TimeoutException {
            // Create a connection factory
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("192.168.112.128");
            factory.setUsername("admin");
            factory.setPassword("123");
            Connection connection = factory.newConnection();
            com.rabbitmq.client.Channel channel = connection.createChannel();
            return channel;
        }
}

Start two worker threads

public class Worker01 {

    // Queue name
    public static final String QUEUE_NAME = "hello";

    // Accept message
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMqUtils.getInstance().getChannel();

        // Accept message parameters
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("Received message:"+new String(message.getBody()));
        };

        // Cancel consumption parameters
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println(consumerTag+"Consumer cancels the callback logic of consumption excuse");
        };

        System.out.println("C2 Waiting to receive message");

        // Message acceptance
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

Start 2 worker threads at the same time:


Producer code

public class Task01 {
    // Queue name
    public static final String QUEUE_NAME = "hello";

    // Send a large number of messages
    public static void main(String[] args) throws Exception
    {
        try (Channel channel = RabbitMqUtils.getInstance().getChannel())
        {
            // Declaration of queue
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            // Enter a message from the console
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNext()) 
            {
                String message = scanner.next();
                channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
                System.out.println("Send message complete:" + message);
            }
        }
    }
}



Message response

concept

  • It may take some time for a consumer to complete a task. What happens if one of the consumers processes a long task and only completes part of it, and suddenly it hangs up. Once RabbitMQ delivers a message to the consumer, it immediately marks the message for deletion. In this case, a consumer suddenly hangs up, and we will lose the message being processed. And subsequent messages sent to the consumer because it cannot be received.
  • In order to ensure that messages are not lost during sending, RabbitMQ introduces a message response mechanism. Message response is: after receiving and processing the message, the consumer tells RabbitMQ that it has been processed, and RabbitMQ can delete the message.

Automatic response

The message is considered to have been successfully transmitted immediately after it is sent. This mode requires a trade-off between high throughput and data transmission security, because in this mode, if the connection or channel is closed on the consumer's side before the message is received, the message will be lost. On the other hand, of course, this mode can deliver overloaded messages on the consumer's side, There is no limit on the number of messages delivered. Of course, this may cause consumers to receive too many messages that are too late to process, resulting in the backlog of these messages, eventually running out of memory, and finally these consumer threads are killed by the operating system, Therefore, this model is only applicable when consumers can process these messages efficiently and at a certain rate.

Manual answer

Method of message response

  • Channel.basicack (for positive confirmation)

RabbitMQ knows the message and processes it successfully. It can be discarded

  • Channel.basicnack (for negative confirmation)
  • Channel.basicreject (used for negative confirmation) is one less parameter than Channel.basicNack. It does not process the message. It is rejected directly and can be discarded.

Multiple explanation

The advantage of manual response is that it can respond in batches and reduce network congestion

true and false of multiple have different meanings:

  • true indicates that unresponsive messages on the channel are answered in batch. For example, there are messages 5, 6, 7 and 8 transmitting tags on the channel. If the current tag is 8, these unresponsive messages of 5-8 will be confirmed to receive the message response
  • false compared with the above, only the messages with tag=8 will be answered, and the three messages 5, 6 and 7 will not be confirmed to receive the message response

Note: a channel can carry more than one message at the same time

Message auto rejoin

If the consumer loses the connection for some reason (its channel has been closed, the connection has been closed or the TCP connection has been lost), resulting in the message not sending ACK confirmation, RabbitMQ will understand that the message has not been fully processed and will queue it again. If other consumers can handle it at this time, it will soon redistribute it to another consumer. In this way, even if a consumer dies occasionally, it can be ensured that no message will be lost.

Message manual answer

1 = = > producer:

//Messages are not lost when answering manually
//If an exception occurs at the consumer, the message is put back into the queue for re consumption
public class Producer
{
    // Queue name
    public static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception{
        Channel channel = RabbitMqUtils.getInstance().getChannel();

        // Declaration queue
        channel.queueDeclare(TASK_QUEUE_NAME,false,false,false,null);

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String message = scanner.next();
            //Use default switch
            channel.basicPublish("",TASK_QUEUE_NAME,null,message.getBytes(StandardCharsets.UTF_8));
            System.out.println("Producer sends message:"+message);
        }

    }
}

2 = = = > consumers 1 and 2

public class ConsumerOne
{
    // Queue name
    public static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception{
        Channel channel = RabbitMqUtils.getInstance().getChannel();
        System.out.println("C1 Short waiting time for acceptance message processing");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            // Sleep for a second
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("The message received is:"+new String(message.getBody()));

            //Conduct manual response
            /*
             * Parameter 1: tag of message
             * Parameter 2: batch response; false: no batch response; true: batch response
             * */
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };


        // Manual response is adopted
        boolean autoAck = false;
        channel.basicConsume(TASK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag) -> {
            System.out.println(consumerTag+"Consumer cancels consumer interface callback logic");
        });
    }
}

public class ConsumerTwo 
{
    // Queue name
    public static final String TASK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws Exception{
        Channel channel = RabbitMqUtils.getInstance().getChannel();
        System.out.println("C2 Long waiting time for receiving message processing");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            // Sleep for a second
            try {
                TimeUnit.SECONDS.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("The message received is:"+new String(message.getBody()));

            //Conduct manual response
            /*
             * Parameter 1: tag of message
             * Parameter 2: batch response; false: no batch response; true: batch response
             * */
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        };


        // Manual response is adopted
        boolean autoAck = false;
        channel.basicConsume(TASK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag) -> {
            System.out.println(consumerTag+"Consumer cancels consumer interface callback logic");
        });
    }
}

3. Test


Consumer 1 receives the message after 1 second

Consumer 2 receives the message after 30 seconds

Because the processing time of ConsumerTwo is too long, we hang it up. At this time, the information will be returned to the queue and distributed to Worker03 for processing.

Stop consumer 2 while processing the message

The message is put back on the queue and consumed by consumer 1

Persistence

concept

We have just seen how to handle the situation that tasks are not lost, but how to ensure that the messages sent by the message producer are not lost after the RabbitMQ service is stopped. By default, when RabbitMQ exits or crashes for some reason, it ignores queues and messages unless told not to do so. Two things need to be done to ensure that messages are not lost: we need to mark both queues and messages as persistent.

Queue persistence

  1. The queues we created before are non persistent. rabbitmq if restarted, the queue will be deleted. If the queue is to be persistent, you need to set the durable parameter to persistent when declaring the queue
 // Declaration queue
 // Persistence requires that the Queue be persistent
 boolean durable = true;
 channel.queueDeclare(TASK_QUEUE_NAME,durable,false,false,null);

  1. Note that if the previously declared queue is not persistent, you need to delete the original queue or re create a persistent queue, otherwise an error will occur

  1. After rerunning the code, the queue persistence succeeds

Message persistence

  • To make the message persistent, you need to modify the code in the message producer, messageproperties, persistent_ TEXT_ Plan adds this attribute.
  • Marking messages as persistent does not completely guarantee that messages will not be lost. Although it tells RabbitMQ to save the message to disk, there is still an interval point where the message is still cached when it is just ready to be stored on disk. There is no real write to disk at this time. The persistence guarantee is not strong, but it is more than enough for our simple task queue. If you need a stronger persistence strategy, refer to the release confirmation section below.
 //Set the message sent by the producer as a persistent message (it is required to be saved to disk)
 channel.basicPublish("",TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN
                      ,message.getBytes(StandardCharsets.UTF_8));
 System.out.println("Producer sends message:"+message);

Unfair distribution

  • At the beginning, we learned that RabbitMQ uses rotation training to distribute messages, but in a certain scenario, this strategy is not very good. For example, there are two consumers processing tasks. One consumer 1 processes tasks very fast, while the other consumer 2 processes tasks very slowly, At this time, we still use the rotation training distribution method. It will come to the conclusion that the consumer with fast processing speed is idle for a large part of the time, while the consumer with slow processing is working all the time. This distribution method is actually not very good in this case, but RabbitMQ does not know this situation, and it still distributes fairly.
  • To avoid this, we can set the parameter channel.basicQos(1)
// Set unfair distribution
int prefetchCount = 1;
channel.basicQos(prefetchCount);

Implementation principle of unfair distribution


This means that if I haven't finished processing this task or I haven't answered you, don't assign it to me. I can only process one task at present, and then rabbitmq will assign the task to the idle consumer who is not so busy. Of course, if all consumers haven't finished their tasks, the queue is still adding new tasks, The queue may be full. At this time, you can only add a new worker or change the strategy of other storage tasks.

The first time the message is distributed to which consumer, it depends on which consumer starts first, and then it will be recorded in the consumer list of rbMQ

channel.basicQos(n); RabbitMQ will send N messages to consumers for the first distribution. After the consumer processes a message and confirms a message, RabbitMQ will reduce the corresponding count by 1, and then the consumer can continue to receive messages until the upper count limit is reached again

1. Turn off automatic response;

2. Change to manual feedback of the completed message;

2. Limit the number of channel.basicQos(n) per occurrence;

Phenomenon: those who can do more work can get more messages for processing

Here is a description of channel.basicQos(n);

For example, the consumer program calls channel.basicQos(5), and then subscribes to a queue for consumption. RabbitM will save a list of consumers. Each message sent will count the corresponding consumers. After the count reaches 5, RabbitMQ will not send messages to this consumer. After the consumer confirms that a message is processed, RabbitMQ reduces the corresponding count by 1, and the consumer can continue to receive the message until the upper limit of the count is reached again. This mechanism can be similar to the "sliding window" in TCP IP

Pre value

The message itself is sent asynchronously, so there must be more than one message on the channel at any time. In addition, the manual confirmation from the consumer is also asynchronous in nature.

Therefore, there is an unacknowledged message buffer here. Therefore, we hope that developers can limit the size of this buffer to avoid the problem of unlimited unacknowledged messages in the buffer.

This can be done by setting the "prefetch count" value using the basic.gos. Method.

This value defines the maximum number of unacknowledged messages allowed on the channel.

Once the number reaches the configured number, RabbitMQ will stop delivering more messages on the channel unless at least one unprocessed message is acknowledged,

For example, suppose there are unacknowledged messages 5, 6, 7 and 8 on the channel, and the prefetch count of the channel is set to 4, RabbitMQ. Will not deliver any messages on the channel unless at least one unacknowledged message is acked.

For example, the message tag=6 has just been acknowledged, and RabbitMQ will sense the situation and send another message.

Message response and QoS pre value have a significant impact on user throughput.

Generally, increasing prefetching will improve the speed of message delivery to consumers.

Although the automatic response transmission message rate is the best, in this case, the number of messages delivered but not processed will also increase, thus increasing the RAM consumption of consumers (random access memory). Care should be taken to use the automatic confirmation mode or manual confirmation mode with infinite preprocessing. Consumers consume a large number of messages if there is no confirmation, It will cause the memory consumption of consumers' connection nodes to become larger, so finding the appropriate pre value is a process of repeated experiments. The value is also different for different loads. The value in the range of 100 to 300 can usually provide the best throughput and will not bring too much risk to consumers.

The pre value of 1 is the most conservative. Of course, this will make the throughput very low, especially when the consumer connection delay is very serious, especially in the environment where the consumer connection waiting time is long.

For most applications, a slightly higher value will be optimal.


Release confirmation

Release confirmation principle

The producer sets the channel to 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, the broker will send a confirmation to the producer (including the unique ID of the message), This allows the producer to know that the message has arrived at the destination queue correctly.

If the message and queue are persistent, the confirmation message will be sent after the message is written to the disk. The delivery tag field of the confirmation message returned by the broker to the producer contains the serial number of the confirmation message. In addition, the broker can also set the multiple field of basic.ack to indicate that all messages before this serial number have been processed.

The biggest advantage of confirm mode is that it is asynchronous. Once a message is published, the producer application can continue to send the next message while waiting for the channel to return the confirmation. After the message is finally confirmed, the producer application can process the confirmation message through the callback method. If RabbitMQ loses the message due to its own internal error, A nack message is sent, and the producer application can also process the nack message in the callback method

Release confirmation policy

How to open release confirmation:

Publishing confirmation is not enabled by default. If you want to enable it, you need to call the method confirmSelect. Whenever you want to use publishing confirmation, you need to call this method in channel

Channel channel = connection.createChannel();
channel.confirmSelect();

Single release confirmation

  • This is a simple confirmation method. It is a synchronous confirmation publishing method, that is, after publishing a message, only it is confirmed to be published, and subsequent messages can continue to be published. The waitForConfirmsOrDie(long) method returns only when the message is confirmed. If the message is not confirmed within the specified time range, it will throw an exception.
  • The biggest disadvantage of this confirmation method is that the publishing speed is particularly slow, because if the published message address is not confirmed, the publishing of all subsequent messages will be blocked. This method provides a throughput of no more than hundreds of published messages per second. Of course, this may be sufficient for some applications.
/*
 * Release confirmation mode,
 * 1,Single confirmation
 * 2,Batch confirmation
 * 3,Asynchronous batch confirmation
 * */
public class ComfirmMessage {

    // Number of batch messages
    public static final int MESSAGE_COUNT = 1000;

    public static void main(String[] args) throws Exception {
        // 1. Single confirmation
        // 1000 individual confirmation messages were issued, taking 567ms
        ComfirmMessage.publishMessageIndividually();

    }

    public static void publishMessageIndividually() throws Exception {
        Channel channel = RabbitMqUtils.getInstance().getChannel();
        String queueName = UUID.randomUUID().toString();
        //Queues in this channel are not persistent, message sharing, or automatically deleted
        channel.queueDeclare(queueName,false,false,false,null);

        // Open release confirmation
        channel.confirmSelect();
        // start time
        long begin = System.currentTimeMillis();

        // Batch message sending
        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String message = i + "";
            //No message persistence
            channel.basicPublish("",queueName,null,message.getBytes(StandardCharsets.UTF_8));
            // Release confirmation of a single message immediately
            boolean flag = channel.waitForConfirms();
            if (flag){
                System.out.println("Message sent successfully");
            }
        }

        // End time
        long end = System.currentTimeMillis();
        System.out.println("release"+MESSAGE_COUNT+"Separate confirmation messages, time consuming"+ (end - begin) + "ms");
    }
}

Batch confirmation release

The above method is very slow. Compared with a single message waiting for confirmation, publishing a batch of messages first and then confirming together can greatly improve the throughput. Of course, the disadvantage of this method is that when a failure causes a problem in publishing, we don't know which message has a problem. We must save the whole batch in memory, To record important information and then republish the message. Of course, this scheme is still synchronous and blocks the release of messages.

/*
 * Release confirmation mode,
 * 1,Single confirmation
 * 2,Batch confirmation
 * 3,Asynchronous batch confirmation
 * */
public class ComfirmMessage {

    // Number of batch messages
    public static final int MESSAGE_COUNT = 1000;

    public static void main(String[] args) throws Exception {
        //2. Batch confirmation
        // It takes 37ms to publish 1000 batch confirmation messages
        ComfirmMessage.publishMessageBatch();
    }

    public static void publishMessageBatch() throws Exception{
        Channel channel = RabbitMqUtils.getInstance().getChannel();
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,false,false,false,null);

        // Open release confirmation
        channel.confirmSelect();
        // start time
        long begin = System.currentTimeMillis();

        // Bulk confirmation message size
        int batchSize = 1000;

        // Batch send batch confirmation
        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String message = i + "";
            channel.basicPublish("",queueName,null,message.getBytes(StandardCharsets.UTF_8));

            // When 100 messages are judged, batch confirmation is performed once
            if (i%batchSize == 0){
                // Confirm release
                channel.waitForConfirms();
            }
        }

        // End time
        long end = System.currentTimeMillis();
        System.out.println("release"+MESSAGE_COUNT+"Batch confirmation messages, time consuming"+ (end - begin) + "ms");
    }
}

Asynchronous publish confirmation

Although the programming logic of asynchronous confirmation is more complex than the above two, it has the highest cost performance. It can not be said whether it is reliable or efficient. It uses callback function to achieve reliable message delivery. This middleware also ensures whether it is delivered successfully through function callback. Let's explain in detail how asynchronous confirmation is realized.

/*
 * Release confirmation mode,
 * 1,Single confirmation
 * 2,Batch confirmation
 * 3,Asynchronous batch confirmation
 * */
public class ComfirmMessage {

    // Number of batch messages
    public static final int MESSAGE_COUNT = 1000;

    public static void main(String[] args) throws Exception {
        //3. Asynchronous batch confirmation
        // Publish 1000 asynchronous confirmation messages, taking 36ms
        ComfirmMessage.publicMessageAsync();

    }

    public static void publicMessageAsync() throws Exception{
        Channel channel = RabbitMqUtils.getInstance().getChannel();
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,false,false,false,null);

        // Open release confirmation
        channel.confirmSelect();
        // start time
        long begin = System.currentTimeMillis();

        // Message confirmation success callback function
        ConfirmCallback ackCallback = (deliveryTag,multiply) -> {
            System.out.println("Confirmed message:"+deliveryTag);
            System.out.println("Batch confirmation:  " + (multiply?"YES":"NO"));
        };

        // Message confirmation failure callback function
        /*
         * Parameter 1: tag of message
         * Parameter 2: batch confirmation
         * */
        ConfirmCallback nackCallback = (deliveryTag, multiply) -> {
            System.out.println("Unacknowledged message:"+deliveryTag);
            System.out.println("Batch confirmation:  " + (multiply?"YES":"NO"));
        };

        // Prepare a message listener to listen for which messages succeed and which messages fail
        /*
         * Parameter 1: which messages are successfully monitored
         * Parameter 2: which messages failed to listen
         * */
        channel.addConfirmListener(ackCallback,nackCallback);

        // Batch send messages
        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String message = "news" + i;
            channel.basicPublish("",queueName,null,message.getBytes(StandardCharsets.UTF_8));
        }

        // End time
        long end = System.currentTimeMillis();
        System.out.println("release"+MESSAGE_COUNT+"Asynchronous acknowledgement messages, time consuming"+ (end - begin) + "ms");
    }

}


Because the success of message publishing and confirmation message sending are two different threads, the listener's execution is asynchronous. Therefore, it can be seen that after 1000 messages are published, the message confirmation information continues

You can use the listener as a single thread in addition to the main thread

How to handle asynchronous unacknowledged messages

The best solution is to put unconfirmed messages into a memory based queue that can be accessed by the publishing thread. For example, use the ConcurrentLinkedQueue queue to transfer messages between confirm callbacks and the publishing thread

At the same time of message publishing, all messages are recorded in a highly concurrent hash table ---- > in the success callback function of the listener, the successfully sent messages are deleted from the hash table ---- > in the failure callback function of the listener, the message of sending failure can be obtained by specifying a key

/*
 * Release confirmation mode,
 * 1,Single confirmation
 * 2,Batch confirmation
 * 3,Asynchronous batch confirmation
 * */
public class ComfirmMessage {

    // Number of batch messages
    public static final int MESSAGE_COUNT = 1000;

    public static void main(String[] args) throws Exception {
        //3. Asynchronous batch confirmation
        // Publish 1000 asynchronous confirmation messages, taking 36ms
        ComfirmMessage.publicMessageAsync();

    }

    public static void publicMessageAsync() throws Exception{
        Channel channel = RabbitMqUtils.getInstance().getChannel();
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,false,false,false,null);

        // Open release confirmation
        channel.confirmSelect();

        /*
         * A thread safe and orderly hash table is suitable for high concurrency
         * 1,Easily associate sequence numbers with messages
         * 2,Easy batch deletion, as long as the serial number is given
         * 3,Support high concurrency
         * */
        ConcurrentSkipListMap<Long,String> outstandingConfirms = new ConcurrentSkipListMap<>();

        // Message confirmation success callback function
        ConfirmCallback ackCallback = (deliveryTag,multiply) -> {
            // Delete the confirmed messages, and the rest are unconfirmed messages

            //If the message is sent in batch, batch deletion is adopted here
            if(multiply)
            {
                ConcurrentNavigableMap<Long, String> confiremed = outstandingConfirms.headMap(deliveryTag);
                confiremed.clear();
            }else {
                //Otherwise, you can delete a single
                outstandingConfirms.remove(deliveryTag);
            }

            System.out.println("Confirmed message:"+deliveryTag);
        };

        // Message confirmation failure callback function
        /*
         * Parameter 1: tag of message
         * Parameter 2: batch confirmation
         * */
        ConfirmCallback nackCallback = (deliveryTag,multiply) -> {
            // Print out the unconfirmed messages
            String message = outstandingConfirms.get(deliveryTag);
            System.out.println("Unacknowledged messages are:" + message +"Unacknowledged messages tag: " + deliveryTag);
        };

        // Prepare a message listener to listen for which messages succeed and which messages fail
        /*
         * Parameter 1: which messages are successfully monitored
         * Parameter 2: which messages failed to listen
         * */
        channel.addConfirmListener(ackCallback,nackCallback);

        // start time
        long begin = System.currentTimeMillis();

        // Batch send messages
        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String message = "news" + i;
            channel.basicPublish("",queueName,null,message.getBytes(StandardCharsets.UTF_8));

            // The sum of all messages to be sent is recorded here
            outstandingConfirms.put(channel.getNextPublishSeqNo(),message);
        }



        // End time
        long end = System.currentTimeMillis();
        System.out.println("release"+MESSAGE_COUNT+"Asynchronous acknowledgement messages, time consuming"+ (end - begin) + "ms");
    }
}

Comparison of three release confirmation speeds

Switch

Introduction to switch

introduce

  • The core idea of RabbitMQ messaging model is that messages produced by producers are never sent directly to queues. In fact, usually producers don't even know which queues these messages are delivered to.
  • On the contrary, the producer can only send messages to the exchange. The work of the switch is very simple. On the one hand, it receives messages from the producer, on the other hand, it pushes them into the queue. The switch must know exactly how to process the received message. Should these messages be put in a specific queue, or should they be put in many queues, or should they be discarded. This depends on the type of switch.

type

Switches are of the following types:

direct = > route, topic, headers, fanout = > publish subscribe mode

Nameless switch:

In the previous section, we knew nothing about exchange, but we were still able to send messages to the queue. The reason why it can be implemented before is that we use the default exchange, which is identified by an empty string (").

The first parameter is the name of the switch. An empty string indicates the default or unnamed switch: messages can be routed to the queue, which is actually specified by the routingKey(bindingkey) binding key, if it exists

channel.basiPublish("","hello",null,message.getBytes());

Temporary queue

  • Whenever we connect to Rabbit, we need a new empty queue. Therefore, we can create a queue with random name, or let the server choose a random queue name for us. Secondly, once we disconnect the consumer, the queue will be deleted automatically.
  • Create a temporary queue as follows:
String queueName = channel.queueDeclare().getQueue();

The created queue length is as follows:


AD stands for temporary queue

Bindings - binds the switch and the specified queue

What is binding? Binding is actually a bridge between exchange and queue. It tells us that exchange is bound to that queue. For example, the following figure tells us × Bound to Q1 and Q2


Fanout - publish subscribe mode

Fanout is a very simple type. As you can guess from the name, it broadcasts all received messages to all queues it knows. By default, there are some exchange types in the system

In short, the message in the Exchange is sent to all queues bound to the Exchange, ignoring the routingKey.

After the producer sends the message to the switch, the switch sends it to the consumer queue. If the consumer queue wants to receive the message in the switch, it needs to ensure that the switch name bound to the queue is consistent with the switch. This is the key of the broadcast mode and the coarsest premise of all subsequent modes of MQ.

such as

1) The manufacturer declares a switch (Exchange). The switch name is "fanoutLogs", the type is broadcast mode "fanout", and the message is "Now you see me";

  //Parameters: switch name, switch type, broadcast mode
 channel.exchangeDeclare("fanoutLogs", "fanout");

2) The consumer declares a queue Q1 and a switch (the name is "fanoutLogs" and the type is "fanout"), and finally the queue is bound to the switch;

channel.exchangeDeclare("fanoutLogs", "fanout");
//Parameters are: queue name; switch name; routingKey ignored
channel.queueBind("Q1", "fanoutLogs", "");

3) The consumer declares another queue Q2, declares a switch (the name is "phantaciLogs" and the type is "fan out"), and finally the queue is bound to the switch;

channel.exchangeDeclare("phantaciLogs", "fanout");
channel.queueBind("Q2", "phantaciLogs", "");

It should be noted that we should start the declaration consumer first and then the declaration producer. Otherwise, if we start the producer first, exchange finds that no queue is interested in it after receiving the message, and willfully discards the message

After Q1 and Q2 are started, start the producer. It can be found that Q1 with the same name as the producer Exchange normally receives messages; Q2 is also in the broadcast mode, but the switch name is different, so no messages are received.

The core idea of RabbitMQ message model: the producer will send messages to RabbitMQ exchange. One side of exchange is the producer and the other side is one or more queues. Exchange determines the life cycle of a message - send it to some queues or discard it directly.

actual combat

1. Consumers

/*
 * messages receiving
 * */
 public class ReceiveLogs01 {
 
     //Switch name
     public static final String EXCHANGE_NAME = "logs";
 
     public static void main(String[] args) throws Exception{
         Channel channel = RabbitMqUtils.getInstance().getChannel();
         //Switch declaration (parameters: switch name; switch type)
         channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
         //Temporary queue
     //Declare a queue with random name. When the consumer disconnects from the queue, the queue will be deleted automatically
     String queueName = channel.queueDeclare().getQueue();

     //Binding switches and queues
      //Parameters are: queue name; switch name; routingKey ignored
     channel.queueBind(queueName,EXCHANGE_NAME,"");
     System.out.println("Wait for the received message and print the received message on the screen...");


     DeliverCallback deliverCallback = (consumerTag, message) -> {
         System.out.println("ReceiveLogs01 The console prints the received message:" + new String(message.getBody()));
     };
     //Declare the messages consumed in the queue (parameters are: queue name; automatic message confirmation; callback for message delivery; consumer cancels the callback for message)
     channel.basicConsume(queueName,true,deliverCallback,consumerTag -> {});
 }
}

/*
 * messages receiving
 * */
 public class ReceiveLogs02 {
    //Switch name
    public static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws Exception{
        Channel channel = RabbitMqUtils.getInstance().getChannel();
        //Switch declaration (parameters: switch name; switch type)
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
        //Temporary queue
        //Declare a queue with random name. When the consumer disconnects from the queue, the queue will be deleted automatically
        String queueName = channel.queueDeclare().getQueue();

        //Binding switches and queues
        //Parameters are: queue name; switch name; routingKey ignored
        channel.queueBind(queueName,EXCHANGE_NAME,"");
        System.out.println("Wait for the received message and print the received message on the screen...");


        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println("ReceiveLogs01 The console prints the received message:" + new String(message.getBody()));
        };
        //Declare the messages consumed in the queue (parameters are: queue name; automatic message confirmation; callback for message delivery; consumer cancels the callback for message)
        channel.basicConsume(queueName,true,deliverCallback,consumerTag -> {});
    }
}

2. Producers

/*
 *  Messaging switch
 * */
 public class Emitlog {
     // Name of the switch
     public  static  final String EXCHANGE_NAME = "logs";
 
     public static void main(String[] args) throws  Exception{
         Channel channel = RabbitMqUtils.getInstance().getChannel();
         //Declare switch (parameters: switch name, switch type, broadcast mode)
         channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

     Scanner scanner = new Scanner(System.in);
     while (scanner.hasNext()){
         String message = scanner.next();
         //The parameters are: switch name; routingKey, ignored. In broadcast mode, the producer can declare the name and type of the switch
         channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes(StandardCharsets.UTF_8));
         System.out.println("Message from producer:"+ message);
     }
     channel.close();
 }
}

Direct -- routing mode

If the producer and consumer have the same Exchange name, switch type and routing key, the consumer can successfully obtain the message.
(PS: compared with the broadcast mode in which messages can be received as long as the switch name, the direct mode adds a layer of routing key on the basis of it.)

The core idea of RabbitMQ message model: the producer will send messages to RabbitMQ exchange. One side of exchange is the producer and the other side is one or more queues. Exchange determines the life cycle of a message - send it to some queues or discard it directly.


In the above figure, we can see that X binds two queues, and the binding type is direct. The binding key of queue Q1 is orange, and the binding key of queue Q2 is green

In this case, the producer publishes the message to exchange, and the message with the binding key orange will be published to queue Q1. Messages with the binding key black or green will be published to queue Q2, and messages of other message types will be discarded.

Multiple binding


Of course, if the binding type of exchange is direct, but the key s of multiple queues it binds are the same, in this case, although the binding type is direct, it behaves a little similar to fanout, just like broadcasting, as shown in the above figure

Multiple routing key reception

By binding multiple routing keys to a consumer, the consumer can receive messages obtained by multiple routes at the same time.

If two routingkeys are bound to the consumer code [DirectWorker], the rest remain unchanged

//routingKey is info
     channel.queueBind("console",EXCHANGE_NAME,"info");
     //routingKey is warning
     channel.queueBind("console",EXCHANGE_NAME,"warning");

actual combat

producer

public class Producer
{
    // Name of the switch
    public  static  final String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws  Exception{
        Channel channel = RabbitMqUtils.getInstance().getChannel();
          //Declare switch: the name of the switch and the type of switch
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String message = scanner.next();
             //Channel publishing message: the name of the switch, secret key: Info --- > sent to the specified queue, and the binary stream of the sent message
            channel.basicPublish(EXCHANGE_NAME,"info",null,message.getBytes(StandardCharsets.UTF_8));
            System.out.println("Message from producer:"+ message);
        }
    }
}

consumer

public class ReceiveLogsDirect01 {
 public static final String EXCHANGE_NAME = "direct_logs";

 public static void main(String[] args) throws Exception {
     Channel channel = RabbitMqUtils.getInstance().getChannel();

     //Declare a queue
     channel.queueDeclare("console",false,false,false,null);

     //Binding switches and queues
     //Parameter: queue name; Switch name; Key routingKey
     channel.queueBind("console",EXCHANGE_NAME,"info");
     channel.queueBind("console",EXCHANGE_NAME,"warning");


     DeliverCallback deliverCallback = (consumerTag, message) -> {
         System.out.println("ReceiveLogsDirect01 The console prints the received message:" + new String(message.getBody()));
     };

     channel.basicConsume("console",true,deliverCallback,consumerTag -> {});
 }
}
public class ReceiveLogsDirect02 {
 public static final String EXCHANGE_NAME = "direct_logs";

 public static void main(String[] args) throws Exception {
     Channel channel = RabbitMqUtils.getInstance().getChannel();

     //Declare a queue
     channel.queueDeclare("disk",false,false,false,null);

     //Binding switches and queues
     channel.queueBind("disk",EXCHANGE_NAME,"error");


     DeliverCallback deliverCallback = (consumerTag, message) -> {
         System.out.println("ReceiveLogsDirect02 The console prints the received message:" + new String(message.getBody()));
     };

     channel.basicConsume("disk",true,deliverCallback,consumerTag -> {});
 }
}

Topic - topic mode

introduce

  • Routing to messages of type topic switch_ The key cannot be written arbitrarily. It must meet certain requirements. It must be a word list separated by a dot. These words can be any word, such as "stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit". Of course, the word list cannot exceed 255 bytes at most.
  • In this rule list, there are two substitutes:
* Can replace a word
# Can replace zero or more words

Matching case

The following figure shows the binding relationship:

Q1 binds a string of three words with orange in the middle: *. Orange*

Q2 binds: the last word is a single word of rabbit: *. *. Rabbit, and the first word is multiple words of lazy: lazy#


Data reception is as follows:

  • quick.orange.rabbit: received by queue Q1Q2
  • quick.orange.fox: received by queue Q1
  • lazy.brown.fox: received by queue Q2
  • lazy.pink.rabbit: Although the two bindings of queue Q2 are satisfied, it will only be received once
  • quick.orange.male.rabbit: four words do not match, and any binding will be discarded

conclusion

  • When the binding key of a queue is #, the queue will receive all data, which is a bit like fanout
  • If there is no # and * in the queue binding key, the queue binding type is direct

actual combat


producer

public class EmitLogTopic {
 //Name of the switch
 public static final String EXCHANGE_NAME = "topic_logs";

 public static void main(String[] args)  throws Exception{
     Channel channel = RabbitMqUtils.getInstance().getChannel();

     HashMap<String, String> map = new HashMap<>();
     map.put("quick.orange.rabbit","By queue Q1Q2 Received");
     map.put("quick.orange.fox","By queue Q1 Received");
     map.put("lazy.brown.fox","By queue Q2 Received ");
     map.put("lazy.pink.rabbit","Although the queue is satisfied Q2 But will only be received once");
     map.put("quick.orange.male.rabbit","If the four words do not match, any binding will be discarded");

     for (Map.Entry<String, String> bindingKeyEntry : map.entrySet()) {
         String routingKey = bindingKeyEntry.getKey();
         String message = bindingKeyEntry.getValue();
          //Message not persistent
         channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes(StandardCharsets.UTF_8));
         System.out.println("Message sent by producer:"+ message );

     }
  }
}

consumer

/*
* Declare the subject switch and related queues
* Consumer C1
* */
public class ReceiveLogsTopic01 {
    //Switch name
    public static final String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] args)  throws Exception{
        Channel channel = RabbitMqUtils.getInstance().getChannel();

        //Claim switch
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        //Declaration queue
        String queueName = "Q1";
        channel.queueDeclare(queueName,false,false,false,null);

        //Queue bundling
        //Set secret key
        channel.queueBind(queueName,EXCHANGE_NAME,"*.orange.*");
        System.out.println("Waiting to receive message......");

        //Callback function after receiving message
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println(new String(message.getBody()));
            System.out.println("Receive queue:"+ queueName + "Binding key:" + message.getEnvelope().getRoutingKey());
        };

        //receive messages
        channel.basicConsume(queueName,true,deliverCallback,consumerTag -> {});
    }
}
/*
* Declare the subject switch and related queues
* Consumer C2
* */
public class ReceiveLogsTopic02 {
    //Switch name
    public static final String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] args)  throws Exception{
        Channel channel = RabbitMqUtils.getInstance().getChannel();

        //Claim switch
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        //Declaration queue
        String queueName = "Q2";
        channel.queueDeclare(queueName,false,false,false,null);

        //Queue bundling
        channel.queueBind(queueName,EXCHANGE_NAME,"*.*.rabbit");
        channel.queueBind(queueName,EXCHANGE_NAME,"*lazy.#");
        System.out.println("Waiting to receive message......");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            System.out.println(new String(message.getBody()));
            System.out.println("Receive queue:"+ queueName + "Binding key:" + message.getEnvelope().getRoutingKey());
        };

        //receive messages
        channel.basicConsume(queueName,true,deliverCallback,consumerTag -> {});
    }
}



Dead letter queue

Concept of dead letter

  • Dead letter, as the name suggests, is a message that cannot be consumed. The literal meaning can be understood as follows. Generally speaking, the producer delivers the message to the broker or directly to the queue. The consumer takes the message out of the queue for consumption, but sometimes some messages in the queue cannot be consumed for specific reasons. If such messages are not processed subsequently, It becomes a dead letter. If there is a dead letter, there will be a dead letter queue.
  • Application scenario: in order to ensure that the message data of the order business is not lost, the dead letter queue mechanism of RabbitMQ needs to be used. When the message consumption is abnormal, the message is put into the dead letter queue. For example, after the user successfully places an order in the mall and clicks to pay, it will automatically become invalid if it is not paid within the specified time

Source of dead letter

  1. Message TTL expired
  2. The queue has reached the maximum length (the queue is full and can no longer add data to mq)
  3. The message is rejected (basic.reject or basic.nack) and request = false

How to deal with dead letter

  • Discard. If it is not very important, you can choose to discard it
  • Record dead letter receipt, and then conduct subsequent business analysis or processing
  • Through the dead letter queue, it is processed by the application responsible for listening for dead letters

How to configure dead letter queue

  • Configure the service queue and bind it to the service switch
  • Configure dead letter switch and routing key for service queue
  • Configure dead letter queue for dead letter switch

Note that it does not directly declare a public dead letter queue, and then all dead letter messages run to the dead letter queue by themselves. Instead, configure a dead letter switch for each service queue that needs to use dead letter. Here, the dead letter switch of the same project can share one, and then assign a separate routing key to each service queue.

With the dead letter switch and routing key, configure the dead letter queue just like configuring the service queue, and then bind it to the dead letter switch.

In other words, the dead letter queue is not a special queue, but a queue bound to the dead letter switch.

Dead letter switch is not a special switch, but a switch used to accept dead letter, so it can be any type of [Direct, Fanout, Topic].

Generally speaking, a unique routing key will be assigned to each service queue, and a dead letter queue will be configured accordingly for listening, that is, a dead letter queue will be configured for each important service queue.

Code architecture diagram

Message TTL expired

Generally speaking, after the message is generated, because the timeout is set, the message will be thrown into the dead letter queue if it is not consumed during this time.

producer

/*
 * Producer code of dead letter queue
 *
 * */
 public class Producer {
 
     //Name of common switch
     public static final String NORMAL_EXCHANGE = "normal_exchange";
     public static void main(String[] args) throws Exception{
         Channel channel = RabbitMqUtils.getInstance().getChannel();
 
         //For dead letter message, set TTL time unit as MS and 10000ms as 10s
         AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();

         for (int i = 0; i < 10; i++) {
             String message = "info" + i;
//Release news
//Publishing to a nonexistent switch will cause an exception to the channel level protocol, which closes the channel,
//Exchange: the exchange to send the message to
//routingKey: routing KEY
//props: other properties of the message, such as routing header, etc
//Body: message body
             channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes(StandardCharsets.UTF_8));
         }
     }
 }

Properties in AMQP.BasicProperties of RabbitMq

The props parameter can give a variety of functional features to the message body.

The properties of BasicProperties are as follows

String contentType, //Type of message content
String contentEncoding, //Encoding format of message content
Map<String,Object> headers,//header switches can be used
Integer deliveryMode,//Message persistence 1 no persistence 2 persistence
Integer priority,//priority
String correlationId, //Association id
String replyTo,//Commonly used to name callback queues
String expiration,//Set expiration time for expired messages
String messageId, //Message id
Date timestamp, //Timestamp of the message
String type,  //type
String userId, //User ID
String appId, //application id 
String clusterId //Cluster id	

RabbitMq AMQP.BasicProperties

consumer

After startup, close 01. The consumer simulates that it cannot receive a message

/*
 * Dead letter queue actual combat
 * Consumer 01
 * */
 public class Consumer01 {
 
     //Common switch name
     public static final String NORMAL_EXCHANGE = "normal_exchange";
 
     //Dead letter switch name
     public static final String DEAD_EXCHANGE = "dead_exchange";
 
     //Common queue name
     public static final String NORMAL_QUEUE = "normal_queue";
 
     //Dead letter queue name
     public static final String DEAD_QUEUE = "dead_queue";
 
     public static void main(String[] args) throws  Exception{
         Channel channel = RabbitMqUtils.getInstance().getChannel();
 
         //Declare dead letter and ordinary switch type as direct
         channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
         channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
 
         //Declare normal queue
         HashMap<String, Object> arguments = new HashMap<>();

         //Set the survival time for each message in the current queue. The unit is milliseconds and the value is a non negative integer
         //Expiration time. The expiration time of all messages is set uniformly in the producer section. There is no need to set it here
         //arguments.put("x-message-ttl",1000);

         //Normal queue setting dead letter queue - the normal queue needs to communicate with the dead letter queue to ensure that the dead letter is delivered to the dead letter queue
         arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
         //Set dead letter RoutingKey
         arguments.put("x-dead-letter-routing-key","lisi");

     //Declare dead letter and normal queue
         // arguments: additional parameter settings to set some characteristics of the current queue and some characteristics of messages in the queue
     channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
     channel.queueDeclare(DEAD_QUEUE,false,false,false,null);

     //Bind ordinary switches to ordinary queues
     channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");
     //Bind the switch of dead letter to the queue of dead letter
     channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
     System.out.println("Waiting to receive message......");

     DeliverCallback deliverCallback = (consumerTag, message) -> {
         System.out.println("Consumer01 The message received is:" + new String(message.getBody()));
     };
     
     channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag->{});
 }
}
/*
 * Dead letter queue actual combat
 * Consumer 02
 * */
 public class Consumer02 {
 
     //Dead letter queue name
     public static final String DEAD_QUEUE = "dead_queue";
 
     
     //The dead letter queue only needs to be bound to the dead letter switch
     public static void main(String[] args) throws  Exception{
         Channel channel = RabbitMqUtils.getInstance().getChannel();
         System.out.println("Waiting to receive message......");
     
     DeliverCallback deliverCallback = (consumerTag, message) -> {
         System.out.println("Consumer02 The message received is:" + new String(message.getBody()));
     };

     channel.basicConsume(DEAD_QUEUE,true,deliverCallback,consumerTag->{});
 }
}

Effect: after starting the producer, 10 messages are sent to NORMAL_QUEUE is then transmitted to DEAD_QUEUE, start consumer 02 and all messages are received.


Common switch binding common queue


Dead letter switch binding dead letter queue

First start the consumer 01 service, declare the general switch, queue and dead letter switch. After the queue, shut down the service, and then turn on the producer. It is found that after the message is routed to the general queue through the general switch, because no one consumes it after 10 seconds, all 10 messages become dead letters and are placed in the dead letter queue

At this time, start the service to consume the messages in the dead letter queue. After consumption, the messages in the dead letter queue are gone because they have been successfully consumed

Summary of queueDeclare() parameter in RabbitMq

Channel binding corresponding message queue function:

queueDeclare(String queue,boolean durable,boolean exclusive,boolean autoDelete,Map<String, Object>arguments)
  • Parameter 1(String queue): Specifies the name of the queue. If the queue does not exist, a queue will be automatically created with the name of the incoming queue;
  • Parameter 2 (Boolean drusable): defines whether the queue feature needs to be persisted. If it is false, the queue will be deleted the next time mq is started. If true, the queue will still exist at the next startup, but it should be noted that the messages in the queue will not be retained. The queue will be empty at the next startup. If you need message persistence, you can specify messageproperties.persistent when passing messages using the basicPublish function_ TEXT_ Plan, which will recover the messages in the queue the next time it is started.
  • Parameter 3(boolean exclusive): whether to monopolize the queue. If true, the queue can only be bound to the current channel, and other channels cannot access the queue
  • Parameter 4(boolean autoDelete): whether to delete the queue automatically. When the consumer consumes the data in the queue and disconnects from the queue, whether to delete the queue
  • Parameter 5 (map < string, Object > arguments): additional parameter settings

It should be noted that a channel can bind multiple queues

Detailed explanation of the parameters of the queueDeclare method

Detailed explanation of common functions in RabbitMQ Channel class

RabbitMQ Channel

Detailed explanation of rabbitmq channel method parameters

Dead letter queue recommended articles

RabbitMq dead letter queue

[RabbitMQ] will take you through RabbitMQ dead letter queue

The queue has reached its maximum length

The message producer code removes the TTL attribute

Maximum number of messages added to the queue by consumer 01

(after startup, close the consumer to simulate that it cannot receive messages). The purpose of startup is to declare the switch and queue

//Set the limit on the number of messages stored in the normal queue
arguments.put("x-max-length",6);

Note that the original queue needs to be deleted at this time because the parameters have changed

Consumer 02 does not need to make changes

Message rejected

Consumer 01

/*
 * Dead letter queue actual combat
 * Consumer 01
 * */
 public class Consumer01 {
 
     //Common switch name
     public static final String NORMAL_EXCHANGE = "normal_exchange";
 
     //Dead letter switch name
     public static final String DEAD_EXCHANGE = "dead_exchange";
 
     //Common queue name
     public static final String NORMAL_QUEUE = "normal_queue";
 
     //Dead letter queue name
     public static final String DEAD_QUEUE = "dead_queue";
 
     public static void main(String[] args) throws  Exception{
         Channel channel = RabbitMqUtils.getInstance().getChannel();
 
         //Declare dead letter and ordinary switch type as direct
         channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
         channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
 
         //Declare normal queue
         HashMap<String, Object> arguments = new HashMap<>();

         //Set the survival time for each message in the current queue. The unit is milliseconds and the value is a non negative integer
         //Expiration time. The expiration time of all messages is set uniformly in the producer section. There is no need to set it here
         //arguments.put("x-message-ttl",1000);

         //Set the limit on the number of messages stored in the normal queue
         //arguments.put("x-max-length",6);
         //Normal queue setting dead letter queue - the normal queue needs to communicate with the dead letter queue to ensure that the dead letter is delivered to the dead letter queue
         arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
         //Set dead letter RoutingKey
         arguments.put("x-dead-letter-routing-key","lisi");

     //Declare dead letter and normal queue
         // arguments: additional parameter settings to set some characteristics of the current queue and some characteristics of messages in the queue
     channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
     channel.queueDeclare(DEAD_QUEUE,false,false,false,null);

     //Bind ordinary switches to ordinary queues
     channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");
     //Bind the switch of dead letter to the queue of dead letter
     channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
     System.out.println("Waiting to receive message......");

     DeliverCallback deliverCallback = (consumerTag, message) ->
     {
         String msg= new String(message.getBody());
         if(msg.equals("info5")){
             System.out.println("Consumer01 Message received" + msg + "And refuse to sign the message");
             //If the request is set to false, it means to refuse to re queue the queue. If dead letter is configured, the switch will send it to the dead letter queue
             channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
         }else {
             //Others that are not rejected shall be answered manually
             System.out.println("Consumer01 Message received"+msg);
             channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
         }
     };
     //Manual start here
     channel.basicConsume(NORMAL_QUEUE,false,deliverCallback,consumerTag->{});
 }
}

Consumer 02 code unchanged

No change in producer code


Tags: Java RabbitMQ Spring Cloud

Posted on Mon, 18 Oct 2021 16:14:58 -0400 by Lee W