1. introduction
In the previous example, each message corresponds to only one consumer. Even if multiple consumers are online, only one consumer will receive and process a message, which is a common way of message middleware. Another way is that the producer produces a message, broadcasts it to all consumers, and all consumers who subscribe to the queue can consume the message. This is message subscription.
The official tutorial lists a scenario where the producer sends a log message, the consumer 1 writes the log to the hard disk after receiving it, and the consumer 2 prints the log to the screen after receiving it. There are still many such scenarios to be explored in the work, and proper use of message subscription can multiply the efficiency.
2. Exchange of RabbitMQ
In the examples in the first two chapters, we cover three concepts
- Producer
- queue
- Consumer
The producer produces the message and sends it to the queue. The consumer gets the message from the queue and consumes it.
This is wrong. In RabbitMQ, the producer does not send a message directly to the queue. In fact, the producer does not even know whether a message will be sent to the queue.
The correct concept is that producers send messages to Rabbit MQ Exchange, where one side is the producer and the other side is one or more queues. Exchange determines the lifecycle of a message -- to some queues, or to discard it directly.
This concept is known in official documents as the core idea of the RabbitMQ message model.
As shown below, X stands for Exchange.
There are four types of Exchange in RabbitMQ
- direct: Compare queue keys by routing key s of messages, and send them equally, often for task distribution between multiple instances of the same application
- The default type is an exchange of direct type, and routing key is automatically set to queue name. Note that direct is not the default type. The default type is when queue does not specify the default handling of exchange. When sending a message, the exchange field should be filled in an empty string accordingly.
- Topics, distributed to queues bound to the exchange through configurable rules, are applicable in scenarios such as geographic location push
- headers: When distribution rules are complex and routing key is not easy to express, ignore routing key and replace it with header. Header can be a non-string, such as Integer or String.
- Distribution to all queues bound to the exchange, ignoring routing key, for MMO games, broadcasting, group chat and other scenarios
For a more detailed introduction, see Official documents
3. Temporary queue
It is very important to name a queue. When consumers consume messages, they need to specify which queue they consume, so that multiple consumers can share a queue at the same time.
String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;
In the above log recording scenario, there are several features
- All consumers need to listen for all log messages, so each consumer needs a separate queue and does not need to share with others.
- Consumers only care about the latest news. Messages before connecting to RabbitMQ need not be concerned. Therefore, each connection needs to create a queue, bind to the corresponding exchange, and delete the queue after the connection is disconnected.
Self-declaring queues is cumbersome, so RabbitMQ provides a simple way to get temporary queues for the above scenarios
String queueName = channel.queueDeclare().getQueue();
This line of code takes a temporary queue with a name similar to "amq.gen-JzTY20BRgKO-HjmUJ0wLg"
4. binding
Again, in RabbitMQ, messages are sent to Exchange, not directly to Queue. Therefore, you need to bind Queue to Exchange and tell RabbitMQ to queue up the messages sent on this Exchange.
Binding queues use this method
Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOException;
Where queue is the queue name, exchange is the exchange center to be bound, and routingKey is the routingKey of the queue.
5. practice
LogSender LogSender
1 import java.io.IOException; 2 import java.util.concurrent.TimeoutException; 3 4 import org.slf4j.Logger; 5 import org.slf4j.LoggerFactory; 6 7 import com.rabbitmq.client.Channel; 8 import com.rabbitmq.client.Connection; 9 import com.rabbitmq.client.ConnectionFactory; 10 11 public class LogSender { 12 13 private Logger logger = LoggerFactory.getLogger(LogSender.class); 14 private ConnectionFactory factory; 15 private Connection connection; 16 private Channel channel; 17 18 /** 19 * Get the connection in the constructor 20 */ 21 public LogSender(){ 22 super(); 23 try { 24 factory = new ConnectionFactory(); 25 factory.setHost("127.0.0.1"); 26 connection = factory.newConnection(); 27 channel = connection.createChannel(); 28 } catch (Exception e) { 29 logger.error(" [X] INIT ERROR!",e); 30 } 31 } 32 /** 33 * Provide a way to shut it down. There's no egg to use right now. 34 * @return 35 */ 36 public boolean closeAll(){ 37 try { 38 this.channel.close(); 39 this.connection.close(); 40 } catch (IOException | TimeoutException e) { 41 logger.error(" [X] CLOSE ERROR!",e); 42 return false; 43 } 44 return true; 45 } 46 47 /** 48 * Business methods of greater concern to us 49 * @param message 50 */ 51 public void sendMessage(String message) { 52 try { 53 //Declare a exchange,Named logs,Type is fanout 54 channel.exchangeDeclare("logs", "fanout"); 55 //exchange yes logs,Represents sending to this Exchange upper 56 //fanout Type exchange,ignore routingKey,So the second parameter is empty. 57 channel.basicPublish("logs", "", null, message.getBytes()); 58 logger.debug(" [D] message sent:"+message); 59 } catch (IOException e) { 60 e.printStackTrace(); 61 } 62 } 63 }
In LogSender, unlike the previous example, we did not declare a Queue directly. Instead, we declared an exchange. When we published the message, the first parameter filled in our declared exchange name, routingKey was left blank, because the fanout type ignored it.
In the previous example, we filled in the queue name by routingKey, because the default exchange (using default exchange center when the first parameter is an empty string) sets the routingKey of the queue to queueName (when declaring the queue, not when sending messages) and is the direct type, so queueName can be used as routingKey to find the queue.
Consumer LogConsumer
1 package com.liyang.ticktock.rabbitmq; 2 3 import java.io.IOException; 4 import java.util.concurrent.TimeoutException; 5 6 import org.slf4j.Logger; 7 import org.slf4j.LoggerFactory; 8 9 import com.rabbitmq.client.AMQP; 10 import com.rabbitmq.client.Channel; 11 import com.rabbitmq.client.Connection; 12 import com.rabbitmq.client.ConnectionFactory; 13 import com.rabbitmq.client.Consumer; 14 import com.rabbitmq.client.DefaultConsumer; 15 import com.rabbitmq.client.Envelope; 16 17 public class LogConsumer { 18 19 private Logger logger = LoggerFactory.getLogger(LogConsumer.class); 20 private ConnectionFactory factory; 21 private Connection connection; 22 private Channel channel; 23 24 /** 25 * Get the connection in the constructor 26 */ 27 public LogConsumer() { 28 super(); 29 try { 30 factory = new ConnectionFactory(); 31 factory.setHost("127.0.0.1"); 32 connection = factory.newConnection(); 33 channel = connection.createChannel(); 34 // statement exchange,Prevent the producer from not starting. exchange Non-existent 35 channel.exchangeDeclare("logs","fanout"); 36 } catch (Exception e) { 37 logger.error(" [X] INIT ERROR!", e); 38 } 39 } 40 41 /** 42 * Provide a way to shut it down. There's no egg to use right now. 43 * 44 * @return 45 */ 46 public boolean closeAll() { 47 try { 48 this.channel.close(); 49 this.connection.close(); 50 } catch (IOException | TimeoutException e) { 51 logger.error(" [X] CLOSE ERROR!", e); 52 return false; 53 } 54 return true; 55 } 56 57 /** 58 * Business methods of greater concern to us 59 */ 60 public void consume() { 61 try { 62 // Get a temporary queue 63 String queueName = channel.queueDeclare().getQueue(); 64 // Bind the queue you just acquired to logs On this exchange center, 65 channel.queueBind(queueName, "logs", ""); 66 //Define a Consumer,consumption Log news 67 Consumer consumer = new DefaultConsumer(channel) { 68 @Override 69 public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, 70 byte[] body) throws IOException { 71 String message = new String(body, "UTF-8"); 72 logger.debug(" [D] I am here to print the log:"+message); 73 } 74 }; 75 //This is automatically confirmed as true,The message is destroyed when it is received 76 channel.basicConsume(queueName, true, consumer); 77 } catch (IOException e) { 78 e.printStackTrace(); 79 } 80 } 81 }
Duplicate a project and change 72 lines to the following code to represent two consumers doing different jobs
1 logger.debug(" [D] I've written the message to my hard drive:"+message);
Consumer App
1 public class App 2 { 3 public static void main( String[] args ) 4 { 5 LogConsumer consumer = new LogConsumer(); 6 consumer.consume(); 7 } 8 }
Producer App
1 public class App { 2 public static void main( String[] args ) throws InterruptedException{ 3 LogSender sender = new LogSender(); 4 while(true){ 5 sender.sendMessage(System.nanoTime()+""); 6 Thread.sleep(1000); 7 } 8 } 9 }
Pack the consumer into two executable jar packages for easy viewing of the console
Execute with the java -jar command, and the results are as follows
6. concluding remarks
This chapter introduces RabbitMQ's switching center. It should be noted that in RabbitMQ, messages are forwarded to the queue through the switching center. Don't be confused by the default exchange. The default exchange automatically sets the queue's name as its routing key, so the queue can be found through queueName when the message is published.
This tutorial is written with reference to official documents. The following chapters will introduce more knowledge about RabbitMQ and practical skills in the project.