Introduction and Application of RabbitMQ

Posted by bmarinho on Wed, 21 Aug 2019 09:54:13 +0200

Articles Catalogue

Overview of RabbitMQ

Message queue: address

Differences in several message queues:

install
1.RabbitMQ relies on erlang, so first install the Erlang environment and configure the environment variables:


2. Execute commands in the RabbitMQ directory

rabbitmq-plugins enable rabbitmq_management\\ Open plug-ins for the management background
rabbitmq-server start
If the previous step reported an error: ERROR: node with name "rabbit" already running on "localhost"
First rabbitmqctl stop and then rabbitmq-server start

3. At this point, we can access through browser: http://localhost:15672 to see the background of RabbitMQ. The default user and password are guest.

Background operation of RabbitMQ

Main interface:

1. Overview

2. Connect, we have no program to connect to this RabbitMQ

Add a user
vhost management
I added a root administrator account, and you can see that in the vhost column there is no permission, vhost is equivalent to a database, we need to authorize users.

  1. First, create a vhost, which usually starts with "/", such as: / vhost_test
  2. Authorization of the user root we created

Java Connection RabbitMQ

1. Old rules, first introduce JAR

		<dependency>
		    <groupId>com.rabbitmq</groupId>
		    <artifactId>amqp-client</artifactId>
		    <version>4.0.2</version>
		</dependency>

2. Connection Tool Class

		public class ConnectionUtil{
		    private static Logger logger = Logger.getLogger(ConnectionUtil.class);
		    
		    public static Connection getConnection(){
		        try{
		            Connection connection = null;
		            //Define a connection factory
		            ConnectionFactory factory = new ConnectionFactory();
		            //Setting the server address (domain name address/ip)
		            factory.setHost("127.0.0.1");
		            //Setting Server Port Number
		            factory.setPort(5672);
		            //Setting up virtual hosts (equivalent to libraries in the database)
		            factory.setVirtualHost("/vhost_test");
		            //Username
		            factory.setUsername("root");
		            //Setting Password
		            factory.setPassword("123456");
		            connection = factory.newConnection();
		            return connection;
		        }
		        catch (Exception e){
		            return null;
		        }
		    }
		}

Then we can get the connection through this tool class, create channels and declare queues, which can be divided into the following categories:

Simple Queue


1. Producer-queue-consumer: producer-to-consumer consumption
Producer:

		/**
		 * Producer
		 * @author admin
		 */
		public class Producter {
			public static void main(String[] args) {
				try{
		            //Get the connection
		            Connection connection = ConnectionUtil.getConnection();
		            //Get a channel from the connection
		            Channel channel = connection.createChannel();
		            //Declaration queue
		            channel.queueDeclare("test_queue", false, false, false, null);
		            String message = "hello mq";
		            //send message
		            channel.basicPublish("", "test_queue", null, message.getBytes("utf-8"));
		            System.out.println("[send]: " + message);
		            channel.close();
		            connection.close();
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}

Consumer:

		/**
		 * Consumer
		 * @author admin
		 */
		public class Consumer {
		
			public static void main(String[] args) {
				try{
		            //Get the connection
		            Connection connection = ConnectionUtil.getConnection();
		            //Get a channel from the connection
		            Channel channel = connection.createChannel();
		            //Declaration queue
		            channel.queueDeclare("test_queue", false, false, false, null);
		            //Defining consumers
		            DefaultConsumer consumer = new DefaultConsumer(channel){
		                //Execute callback method when message arrives
		                @Override
		                public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,byte[] body) throws IOException{
		                    String message = new String(body, "utf-8");
		                    System.out.println("[Receive]: " + message);
		                }
		            };
		            //listen queue
		            channel.basicConsume("test_queue", true, consumer);
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}


Running the producer, you can see the queue in the RabbitMQ background with a message:

We run consumers again:

At the same time, you can see that the message is used and that there is no more in the queue:

In this way, we have realized the connection and use of RabbitMQ, but the shortcomings of this simple queue are also obvious:

  • Multiple consumers are not supported to use messages in the same queue, and if the queue name changes, they have to change at the same time.

Work queue

Polling distribution

2. Producer-queue-multiple consumers, i.e. Work Queues
Modify the producer to generate multiple messages:

		public class Producter {
			public static void main(String[] args) {
				try{
		            Connection connection = ConnectionUtil.getConnection();
		            Channel channel = connection.createChannel();
		            channel.queueDeclare("test_queue", false, false, false, null);

		             for (int i = 0; i < 50; i++) {
			            String message = "message_" + i;
			            System.out.println("[send]: " + message);
			            channel.basicPublish("", "test_queue", null, message.getBytes());
			            Thread.sleep(i * 20);
			        }
		            channel.close();
		            connection.close();
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}

2. Consumers plus waiting time, one 1s, one 3s:
Consumer 1

		public class Consumer1 {
			public static void main(String[] args) {
				try{
		            Connection connection = ConnectionUtil.getConnection();
		            Channel channel = connection.createChannel();
		            channel.queueDeclare("test_queue", false, false, false, null);
		            DefaultConsumer consumer = new DefaultConsumer(channel){
		                @Override
		                public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,byte[] body) throws IOException{
		                    String message = new String(body, "utf-8");
		                    System.out.println("[Receive]: " + message);
		                }
		            };
		            try {
		                Thread.sleep(1000);
		            } catch (InterruptedException e) {
		                e.printStackTrace();
		            }
		            channel.basicConsume("test_queue", true, consumer);
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}

Consumer 2

		public class Cunsumer2 {
			public static void main(String[] args) {
				try{
		            Connection connection = ConnectionUtil.getConnection();
		            Channel channel = connection.createChannel();
		            channel.queueDeclare("test_queue", false, false, false, null);
		            DefaultConsumer consumer = new DefaultConsumer(channel){
		                @Override
		                public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,byte[] body) throws IOException{
		                    String message = new String(body, "utf-8");
		                    System.out.println("[Receive]: " + message);
		                }
		            };
		            try {
		                Thread.sleep(3000);
		            } catch (InterruptedException e) {
		                e.printStackTrace();
		            }
		            channel.basicConsume("test_queue", true, consumer);
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}

First, we open two consumer threads, because there is no content in the queue, so it will block. Then we open the producer threads. The results are as follows:

Although the processing time is different, the number of messages received by two consumers is the same. This method is called round-robin. No matter who is busy, it will not give more messages. It is always you and me.

Fair distribution

In order to achieve fair distribution, we need to prohibit the automatic response of the channel. Look at this code:

		channel.basicConsume(QUEUE_NAME, true, consumer);

Here, true, in fact, is the value of autoACK, set it to false, we manually respond:

		public class Consumer {
			private static final String QUEUE_NAME = "test_queue";
			public static void main(String[] args) {
				try{
		            Connection connection = ConnectionUtil.getConnection();
		            final Channel channel = connection.createChannel();
		            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
		            //Before each consumer returns the confirmation message, the message queue does not send the next message to the consumer, guaranteeing to send one message at a time.
            		channel.basicQos(1);
		            DefaultConsumer consumer = new DefaultConsumer(channel){
		                @Override
		                public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,byte[] body) throws IOException{
		                    String message = new String(body, "utf-8");
		                    System.out.println("[Receive1]: " + message);
		                    try {
		                        Thread.sleep(1000);
		                    } catch (InterruptedException e) {
		                        e.printStackTrace();
		                    }
		                    channel.basicAck(envelope.getDeliveryTag(),false);//Manual Response
		                }
		            };
		            channel.basicConsume(QUEUE_NAME, false, consumer);//Cancel automatic response
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}

The results are as follows: those who can work harder than others.

Benefits of fair distribution over polling distribution:

  • When polling is distributed and messages are distributed to consumers, messages will be deleted from memory. If the consumer hangs up at this time, the messages will be lost.
  • Fair distribution, on the other hand, deletes the information in memory only after waiting for a message response, and if there is a problem, it will be handed over to other consumers.

Subscriber mode


Patterns: A producer, multiple consumers and consumers have their own queues. Messages are sent to the switch exchange first. Each queue is bound to the switch to realize that a message is consumed by multiple consumers.
Switches: Receive producer messages and push messages to queues
There are three types of switches:

  • Fanout
  • Direct
  • Topic
fanout

Without processing routing keys, queues are bound to switches and forwarded directly to all queues

Producer
The producer specifies exchange (switch) and routing key when sending messages outward, but does not specify queue (queue) or bind queue (queue) to exchange.

	public class Producter {
		private static final String EXCHANGE_NAME = "test_exchange_fanout";
		public static void main(String[] args) {
			try{
				Connection connection = ConnectionUtil.getConnection();
		        Channel channel = connection.createChannel();
	
		        //Declare the switch, type fanout
		        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
	
		        String msg = "hello mq!";
	
		        channel.basicPublish(EXCHANGE_NAME,"info",null,msg.getBytes());//Sending method is different from above!!!
	
		        System.out.println("[send]:"+ msg);
		        channel.close();
		        connection.close();
	        }
	        catch (Exception e){
	            e.printStackTrace();
	        }
		}
	}

Consumer 1
When consumers consume messages, they need to declare queues (with random queue names) and bind them to switches.

Note the queueBind method's parameter order, the queue and switch order can not be changed, otherwise the error will be reported:

		public class Consumer {
			private static final String EXCHANGE_NAME = "test_exchange_fanout";
			private static final String QUEUE_NAME = "test_queue_fanout_email";
			public static void main(String[] args) {
				try{
		            Connection connection = ConnectionUtil.getConnection();
		            final Channel channel = connection.createChannel();
		            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
		            //Switch Binding Queue
		            channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
		            
		            channel.basicQos(1);
		            
		            DefaultConsumer consumer = new DefaultConsumer(channel){
		                @Override
		                public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,byte[] body) throws IOException{
		                    String message = new String(body, "utf-8");
		                    System.out.println("[Receive1]: " + message);
		                    try {
		                        Thread.sleep(1000);
		                    } catch (InterruptedException e) {
		                        e.printStackTrace();
		                    }finally {
		                    	channel.basicAck(envelope.getDeliveryTag(),false);
							}
		                }
		            };
		            channel.basicConsume(QUEUE_NAME, false, consumer);
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}

Consumer 2

		public class Cunsumer2 {
			private static final String EXCHANGE_NAME = "test_exchange_fanout";
			private static final String QUEUE_NAME = "test_queue_fanout_sms";
			public static void main(String[] args) {
				try{
		            Connection connection = ConnectionUtil.getConnection();
		            final Channel channel = connection.createChannel();
		            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
		            channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
		            
		            channel.basicQos(1);
		            DefaultConsumer consumer = new DefaultConsumer(channel){
		                @Override
		                public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,byte[] body) throws IOException{
		                    String message = new String(body, "utf-8");
		                    System.out.println("[Receive2]: " + message);
		                    try {
		                        Thread.sleep(3000);
		                    } catch (InterruptedException e) {
		                        e.printStackTrace();
		                    }finally {
		                    	channel.basicAck(envelope.getDeliveryTag(),false);
							}
		                }
		            };
		            channel.basicConsume(QUEUE_NAME, false, consumer);
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}

Result:

direct

According to the bound routing key, which key the message carries, it is routed to which queue. Multiple keys can be bound in one queue, as follows:

Producer
Be careful to change the name of the switch first.

		public class Producter{
			private static final String EXCHANGE_NAME = "test_exchange_direct";
			public static void main(String[] args) {
				try{
					Connection connection = ConnectionUtil.getConnection();
			        Channel channel = connection.createChannel();
		
			        channel.exchangeDeclare(EXCHANGE_NAME,"direct");
		
			        String msg = "hello direct!";
		
			        String routingKey = "error";
		
			        channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes());
		
			        System.out.println("[send]:"+ msg);
			        channel.close();
			        connection.close();
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}

Consumer
The declared queue is bound to exchange by routing key so that data can be received, as shown in the figure above, as follows:

		//Consumer 1, Switch Binding Queue
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
        //Consumer 2
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"info");
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"warning");

Result
1. The producer routingKey is error:

2. The producer routingKey is info:

topics

Pattern matching can be achieved
Character Matching: # Matches one or more, and * Matches one

Producer

		public class Producter{
			private static final String EXCHANGE_NAME = "test_exchange_topic";
			public static void main(String[] args) {
				try{
					Connection connection = ConnectionUtil.getConnection();
			        Channel channel = connection.createChannel();
		
			        channel.exchangeDeclare(EXCHANGE_NAME,"topic");
		
			        String msg = "hello topic!";
			        String routingKey = "routingKey.one";//Be careful
			        channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes());
			        System.out.println("[send]:"+ msg);
			        channel.close();
			        connection.close();
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}

Consumer

		//Consumer 1
		channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"routingKey.#");
		//Consumer 2
		channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"routingKey.two");
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"routingKey.three");

Result
1. The producer routingKey is routingKey.one:

2. The producer routingKey is routingKey.

Message persistence

When declaring a queue, set persistence:

		boolean durable = true;
		channel.queueDeclare(QUEUE_NAME,durable,false,false,null);

Note: rabbitmq does not support queues that change their existing names. It is not possible to change directly to false. That is to say rabbitmq does not support redefining an existing queue.

We solve the problem of abnormal data loss of rabbitMQ server by persistent data, but the producer also knows whether the message is successfully sent to the rabbitMQ server, so as to ensure that the data is not really lost.

There are two ways to confirm:

AMQP Protocol
In fact, the operation of sending messages by generators is transformed into a transaction:

		try{
			channel.txSelect();//Declarations
		    channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes());
		    System.out.println("[send]:"+ msg);
		    channel.txCommit();//Submission transaction
		}catch(Exception e){
			channel.txRollback();//Roll back transactions
		}

confirm mode
Confirm sender validation mode is similar to transactions, but it performs better than using transactions to ensure data reliability.

Realization principle:
The producer sets channel to confirm mode, and all messages posted on that channel will be assigned a unique ID (starting from 1).

  • After the message is delivered to all matching queues, the broker sends a confirmation to the producer (including the unique ID of the message)
  • If the message and queue are persistent, the confirmation message will be sent after the message is written to disk. The delivery-tag field in the confirmation message returned to the producer contains the serial number of the confirmation message. In addition, broker can set the multiple field of basic.ack to indicate that all messages before the serial number have been sent. It has been treated.

Three implementations of Confirm:

  • channel.waitForConfirms(): In the normal sender confirmation mode, each message is sent, the waitForConfirms() method is called to wait for the server to confirm. If the server returns false or does not return within the timeout, the client retransmits the message.
		public class Producter{
			private static final String QueueName = "test_confirm";
			public static void main(String[] args) {
				try{
					Connection connection = ConnectionUtil.getConnection();
			        final Channel channel = connection.createChannel();
			        
			        channel.queueDeclare(QueueName, true, false, false, null);//Remember to turn on persistence
			        channel.confirmSelect();//Set channel to confirm mode
			        
			        String msg = "hello topic";
			        String routingKey = "info";
		
			        for(int i=0;i < 10;i++){
			        	channel.basicPublish("", QueueName, null, (msg+"_"+i).getBytes());
			        	if (channel.waitForConfirms()) {//Ordinary sender confirmation mode
			        		System.out.println(msg+"_"+i);
				        }
			        }
			        channel.close();
			        connection.close();
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}
  • channel.waitForConfirmsOrDie(): In batch confirmation mode, after each batch of messages is sent, the waitForConfirms() method is invoked to execute the following code after all messages are sent in a synchronous manner, and an IOException exception is thrown as long as one message is not confirmed.
		public class Producter{
			private static final String QueueName = "test_confirm";
			public static void main(String[] args) {
				try{
					Connection connection = ConnectionUtil.getConnection();
			        final Channel channel = connection.createChannel();
		
			        channel.queueDeclare(QueueName, true, false, false, null);
			        channel.confirmSelect();//Set channel to confirm mode
			        
			        String msg = "hello topic";
			        String routingKey = "info";
	
			        for(int i=0;i< 10;i++){
			        	channel.basicPublish("", QueueName, null, (msg+"_"+i).getBytes());
			        }
			        channel.waitForConfirmsOrDie();//Batch Confirmation Model
			        System.out.println("Complete execution");
			        channel.close();
			        connection.close();
		        }
		        catch (Exception e){
		            e.printStackTrace();
		        }
			}
		}
  • channel.addConfirmListener(): Asynchronously listen for sender confirmation mode. The code is asynchronously executed. Message confirmation may be batch confirmation. Whether batch confirmation is based on the parameter of multiple returned. This parameter is bool value, if true means that all messages before the value of deliveryTag were executed in batch, if FALS E means a single confirmation
		//Channel channel calls back handleAck method set on broker's ack message and handleNack method set on broker's ack message after receiving nack message.
		channel.addConfirmListener(new ConfirmListener() {
			@Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
            	System.out.println("nack: deliveryTag = "+deliveryTag+" multiple: "+multiple);
            }
                
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
            	System.out.println("ack: deliveryTag = "+deliveryTag+" multiple: "+multiple);
            }
		});

Topics: RabbitMQ Java Erlang Database