RabbitMQ learning notes 04--ttl -- dead letter queue -- cluster construction

Posted by adamlacombe on Mon, 31 Jan 2022 19:35:53 +0100

01 expiration time TTL

The expiration time TTL indicates that the expected time can be set for the message, and within this time, it can be received and obtained by the consumer; After that, the message will be deleted automatically. RabbitMQ can set TTL for messages and queues. At present, there are two methods to set.

The first method is to set the queue attribute, and all messages in the queue have the same expiration time.
The second method is to set the message separately, and the TTL of each message can be different.

If the above two methods are used at the same time, the expiration time of the message shall be subject to the value with the smaller TTL between them. Once the lifetime of the message in the queue exceeds the set TTL value, it is called dead message. It is delivered to the dead letter queue, and the consumer will no longer receive the message.

Queue ttl

Configure ttl queue

@Configuration
public class TTLRabbitMQConfiguration {
    
    //Switch
    @Bean
    public DirectExchange ttlDirectExchange(){
        return new DirectExchange("ttl_direct_exchange", true, false);
    }
    
    //queue
    @Bean
    public Queue directTtlQueue(){
        //Set expiration time
        Map<String, Object> args = new HashMap<>();
        args.put("x-message-ttl", 5000); //Note that this must be an int type
        return new Queue("ttl.direct.queue", true, false, false, args);
    }
    
    //binding
    @Bean
    public Binding directTTLSmsBinding(){
        return BindingBuilder.bind(directTtlQueue()).to(ttlDirectExchange()).with("ttl");
    }
    
}

Producer method

public void makeOrderTtl(int userId, int productId, int num) {
        //1. Query whether the inventory is sufficient according to the commodity id
        //2. Save order
        String orderId = UUID.randomUUID().toString();
        System.out.println("Order production succeeded" + orderId);
        //3. Complete message distribution through MQ
        String exchangeName = "ttl_direct_exchange";
        String routingKey = "ttl";
        /*Distribution method
         * param1 Switch
         * param2 Routing key/Queue name
         * param3 Message content
         * */
        rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);

Run the tests of the producer module and send messages to the ttl queue
From the Web side, you can see that the message expires and disappears after 5 seconds


Message ttl

Add normal queue

@Bean
    public Queue directTtlMessageQueue(){

        return new Queue("ttl.message.direct.queue", true);
    }
    @Bean
    public Binding directTTLMessageBinding(){
        return BindingBuilder.bind(directTtlMessageQueue()).to(ttlDirectExchange()).with("ttlMessage");
    }

Production method

public void makeOrderTtlMessage(int userId, int productId, int num) {
        //1. Query whether the inventory is sufficient according to the commodity id
        //2. Save order
        String orderId = UUID.randomUUID().toString();
        System.out.println("Order production succeeded" + orderId);
        //3. Complete message distribution through MQ
        String exchangeName = "ttl_direct_exchange";
        String routingKey = "ttlMessage";

        //Set expiration time for messages
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("5000");//Here is the String type
                message.getMessageProperties().setContentEncoding("UTF-8");
                return message;
            }
        };

        /*Distribution method
         * param1 Switch
         * param2 Routing key/Queue name
         * param3 Message content
         * */
        rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId, messagePostProcessor);
    }

Note: the expired messages will not be moved to the dead letter queue after expiration, but the expired messages in the queue with ttl can be transferred to the dead letter queue

02 dead letter queue

DLX, fully known as dead letter exchange, can be called dead letter switch or dead letter mailbox. When a message becomes dead message in a queue, it can be re sent to another switch, which is DLX. The queue bound to DLX is called dead message queue.
The message may become a dead letter due to the following reasons:

  • Message rejected
  • Message expiration
  • The queue has reached its maximum length

DLX is also a normal switch. It is no different from a general switch. It can be specified on any queue. In fact, it is to set the properties of a queue. When there is a dead letter in this queue, Rabbitmq will automatically republish the message to the set DLX, and then it will be routed to another queue, that is, the dead letter queue.
To use dead letter queue, you only need to set the queue parameter x-dead-letter-exchange to specify the switch when defining the queue.

Dead letter queue and switch configuration

@Configuration
public class DeadRabbitMQConfiguration {

    //Switch
    @Bean
    public DirectExchange deadDirectExchange(){
        return new DirectExchange("dead_direct_exchange", true, false);
    }

    //queue
    @Bean
    public Queue deadQueue(){
        return new Queue("dead.direct.queue", true);
    }

    //binding
    @Bean
    public Binding deadBinding(){
        return BindingBuilder.bind(deadQueue()).to(deadDirectExchange()).with("dead");
    }

}

Only ordinary switches and ordinary queues. To specify them as dead letter queues and dead letter switches, you need to specify them in ttl queue configuration

Configure these two parameters

//queue
    @Bean
    public Queue directTtlQueue(){
        //Set expiration time
        Map<String, Object> args = new HashMap<>();
        args.put("x-message-ttl", 5000); //Note that this must be an int type
        args.put("x-dead-letter-exchange", "dead_direct_exchange");
        args.put("x-dead-letter-routing-key", "dead");
        return new Queue("ttl.direct.queue", true, false, false, args);
    }


Once the queue parameter is created, an error will not be reported. Therefore, once the queue parameter is modified, it will not be overwritten

After the expiration time, TTL direct. The messages in the queue will be put into the dead letter queue

Set TTL direct. The maximum message length of the queue. If it exceeds the length, the message will be rejected and put into the private message queue

//queue
    @Bean
    public Queue directTtlQueue(){
        //Set expiration time
        Map<String, Object> args = new HashMap<>();
        //args.put("x-message-ttl", 5000); // Note that this must be an int type
        args.put("x-max-length", 5);
        args.put("x-dead-letter-exchange", "dead_direct_exchange");
        args.put("x-dead-letter-routing-key", "dead");
        return new Queue("ttl.direct.queue", true, false, false, args);
    }

Loop eleven messages

 @Test
    public void testTtlDirect(){
        for (int i = 0; i < 11; i++)
            orderService.makeOrderTtl(1, 1, 12);
    }

result:

Six rejected messages were forwarded to the dead letter queue

03 RabbitMQ memory disk monitoring

Memory warning for RabbitMQ
When the memory usage exceeds the configured threshold or the remaining disk space exceeds the configured threshold, RabbitMQ will temporarily block the connection of the client and stop receiving messages from the client, so as to avoid the collapse of the server, and the mentality detection mechanism between the client and the server will also fail.


In overviews, 137MB of memory is displayed. If it reaches 735M, a warning will be given.
Persistence: the process of synchronizing data in memory to disk.
After the explosion, the message queue can no longer receive messages.

Memory control of RabbitMQ
Refer to the help documentation: https://www.rabbitmq.com/configure.html
When a warning occurs, it can be modified and adjusted through configuration
Command mode:

rabbitmqctl set_vm_memory_high_watermark <fraction>
rabbitmqctl set_vm_memory_high_watermark absolute 50MB

fraction/value is the memory threshold. The default is 0.4/2GB, which means that when the memory of RabbitMQ exceeds 40%, a warning will be generated and the connection of all producers will be blocked. The threshold value modified by this command will be invalid after the broker is restarted. The threshold value set by modifying the configuration file will not disappear with the restart, but it will take effect only after the broker is restarted as the configuration file is modified.

How to configure files:
Configuration file: / etc / rabbitmq / rabbitmq conf

#default
#vm_memory_high_watermark.relative = 0.4
# Use the relative value to set fraction. The recommended value is between 04 and 0.7, and it is not recommended to exceed 0.7
vm_memory_high_watermark.relative = 0.6
# The absolute value of absolute is used, but it is KB, MB and GB. The corresponding commands are as follows
vm_memory_high_watermark.absolute = 2GB

RabbitMQ memory page feed
Before a Broker node and memory blocking producer, it will try to page the messages in the queue to disk to free up memory space. Both persistent and non persistent messages will be written to disk. The persistent message itself has a copy in the disk, so the persistent messages will be cleared from memory during the transfer process.
By default, when the memory reaches the threshold of 50%, page feed will be processed.
In other words, when the memory threshold is 0.4 by default, when the memory exceeds 0.40.5 = 0.2, page feed will be performed.
By default, when the memory reaches the threshold of 50%, page feed will be processed.
In other words, when the memory threshold is 0.4 by default, when the memory exceeds 0.40.5 = 0.2, page feed will be performed.
You can set VM by_ memory_ high_ watermark_ paging_ Ratio to adjust.

vm_memory_high_watermark.relative = 0.4
vm_memory_high_watermark_paging_ratio = 0.7(Set a value less than 1)

Disk alert for RabbitMQ
When the remaining disk space is lower than the determined threshold, RabbitMQ will also block the producer, which can avoid the server crash caused by running out of disk space due to the continuous page change of non persistent messages.
By default: when the disk alert is 50MB, the alert will be performed. Indicates that the current disk space of 50MB will block the producer and stop the process of page feed of memory messages to the disk.
This threshold can be reduced, but it can not completely eliminate the possibility of crash caused by disk exhaustion. For example, in the gap between two disk space checks, the first check is 60MB, and the second check may be 1MB, and a warning will appear.
Modify by command:

rabbitmqctl set_disk_free_limit  <disk_limit>
rabbitmqctl set_disk_free_limit memory_limit  <fraction>
disk_limit: Fixed unit KB MB GB
fraction : Is the relative threshold. The recommended range is:1.0~2.0 between. (relative to memory)

Modify through configuration file:

disk_free_limit.relative = 3.0
disk_free_limit.absolute = 50mb

RabbitMQ cluster construction

RabbitMQ, a message queuing middleware product, is written based on Erlang. Erlang language is naturally distributed (realized by synchronizing magic cookie s of each node of Erlang cluster). Therefore, RabbitMQ naturally supports clustering. This makes it unnecessary for RabbitMQ to implement the HA scheme and save the metadata of the cluster through ZooKeeper like ActiveMQ and Kafka. Clustering is a way to ensure reliability. At the same time, it can increase message throughput through horizontal expansion.
In the actual use process, multi machine and multi instance deployment method is adopted. In order to facilitate students to practice building, sometimes you have to build a rabbitmq cluster on one machine. This chapter mainly focuses on single machine and multi instance.

Main reference official documents: https://www.rabbitmq.com/clustering.html

CentOS builds rabbitmq cluster

Installing RabbitMQ

New path:

[root@iZ8vbhcwtixrzhsn023sghZ ~]# mkdir /usr/rabbitmq -p
[root@iZ8vbhcwtixrzhsn023sghZ ~]# cd /usr/rabbitmq/

Erlang installation

wget https://packages.erlang-solutions.com/erlang-solutions-2.0-1.noarch.rpm
rpm -Uvh erlang-solutions-2.0-1.noarch.rpm
yum install -y erlang

Install Scot

yum install -y socat

Installing rabbitmq

 wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.13/rabbitmq-server-3.8.13-1.el8.noarch.rpm
 rpm -Uvh rabbitmq-server-3.8.13-1.el8.noarch.rpm

Start rabbitmq

# Start service
> systemctl start rabbitmq-server
# View service status
> systemctl status rabbitmq-server
# Out of Service
> systemctl stop rabbitmq-server
# Startup service
> systemctl enable rabbitmq-server

Check whether rabbitmq is working normally

ps aux|grep rabbitmq


Stop rabbitmq

Suppose there are two rabbit MQ nodes, rabbit-1 and rabbit-2. Rabbit-1 is the master node and rabbit-2 is the slave node.

Start the first node rabbit-1

sudo RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit-1 rabbitmq-server start &


Start the second node rabbit-2

sudo RABBITMQ_NODE_PORT=5673 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}]" RABBITMQ_NODENAME=rabbit-2 rabbitmq-server start &


Review the rabbitmq process again

Use rabbit-1 as the master node

#Stop application
> sudo rabbitmqctl -n rabbit-1 stop_app
#The purpose is to clear the historical data on the node (if it is not cleared, the node cannot be added to the cluster)
> sudo rabbitmqctl -n rabbit-1 reset
#Start application
> sudo rabbitmqctl -n rabbit-1 start_app

Using rabbit-2 as the slave node

# Stop application
> sudo rabbitmqctl -n rabbit-2 stop_app
# The purpose is to clear the historical data on the node (if it is not cleared, the node cannot be added to the cluster)
> sudo rabbitmqctl -n rabbit-2 reset
# Add rabbit2 node to rabbit1 cluster [host name of server node server]
> sudo rabbitmqctl -n rabbit-2 join_cluster rabbit-1@'Server-node'
# Start application
> sudo rabbitmqctl -n rabbit-2 start_app

Operate rabbitmqctl instruction on rabbitmq, - n followed by node name


Verify cluster status

sudo rabbitmqctl cluster_status -n rabbit-1


Web view
The default is to close the Web graphical interface and open it

rabbitmq-plugins enable rabbitmq_management

Set account and password for two cluster nodes

rabbitmqctl -n rabbit-1 add_user admin admin
rabbitmqctl -n rabbit-1 set_user_tags admin administrator
rabbitmqctl -n rabbit-1 set_permissions -p / admin ".*" ".*" ".*"

rabbitmqctl -n rabbit-2 add_user admin admin
rabbitmqctl -n rabbit-2 set_user_tags admin administrator
rabbitmqctl -n rabbit-2 set_permissions -p / admin ".*" ".*" ".*"

docker builds rabbitmq cluster

Create mapped data volume directory

[root@iZ8vbhcwtixrzhsn023sghZ ~]# cd /
[root@iZ8vbhcwtixrzhsn023sghZ /]# mkdir rabbitmqcluster
[root@iZ8vbhcwtixrzhsn023sghZ /]# cd rabbitmqcluster/
[root@iZ8vbhcwtixrzhsn023sghZ rabbitmqcluster]# mkdir rabbitmq01 rabbitmq02 rabbitmq03

Create and run three rabbitmq node containers

docker run -d --hostname rabbitmq01 --name rabbitmqcluster01 -p 15672:15672 -p 5672:5672 -e RABBITMQ_ERLANG_COOKIE='rabbitmq_cookie' rabbitmq:management

docker run -d --hostname rabbitmq02 --name rabbitmqcluster02 -p 5673:5672 --link rabbitmqcluster01:rabbitmq01 -e RABBITMQ_ERLANG_COOKIE='rabbitmq_cookie' rabbitmq:management

docker run -d --hostname rabbitmq03 --name rabbitmqcluster03 -p 5674:5672 --link rabbitmqcluster01:rabbitmq01 --link rabbitmqcluster02:rabbitmq02  -e RABBITMQ_ERLANG_COOKIE='rabbitmq_cookie' rabbitmq:management

After the container is successfully started, it can be accessed
http: / / (server ip):15672
http: / / (server ip):15673
http: / / (server ip):15674
Web management interface

Container nodes join the cluster

Enter the first rabbitmq node container

docker exec -it rabbitmqCluster01 bash

Complete the operation of restarting and clearing historical data

rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
exit


Enter the second node container and execute the following command:

[root@iZ8vbhcwtixrzhsn023sghZ rabbitmqcluster]# docker exec -it rabbitmqCluster02 bash
root@rabbitmq02:/# rabbitmqctl stop_app
Stopping rabbit application on node rabbit@rabbitmq02 ...
root@rabbitmq02:/# rabbitmqctl reset
Resetting node rabbit@rabbitmq02 ...
root@rabbitmq02:/# rabbitmqctl join_cluster rabbit@rabbitmq01
Clustering node rabbit@rabbitmq02 with rabbit@rabbitmq01
root@rabbitmq02:/# rabbitmqctl start_app
Starting node rabbit@rabbitmq02 ...
 completed with 3 plugins.
root@rabbitmq02:/# exit
exit

Then there is the third node:

[root@iZ8vbhcwtixrzhsn023sghZ ~]# docker exec -it rabbitmqCluster03 bash
root@rabbitmq03:/# rabbitmqctl stop_app
Stopping rabbit application on node rabbit@rabbitmq03 ...
root@rabbitmq03:/# rabbitmqctl reset
Resetting node rabbit@rabbitmq03 ...
root@rabbitmq03:/# rabbitmqctl join_cluster --ram rabbit@rabbitmq01
Clustering node rabbit@rabbitmq03 with rabbit@rabbitmq01
root@rabbitmq03:/# rabbitmqctl start_app
Starting node rabbit@rabbitmq03 ...
 completed with 3 plugins.
root@rabbitmq03:/# exit
exit

Topics: Java RabbitMQ Distribution