RabbitMQ Guide 3: Publish/Subscribe Mode (Publish/Subscribe)

In the previous chapter, we created a work queue. The idea of ​​the work queue pattern is that each message will only be forwarded to one consumer. This chapter will explain a completely different scenario: we will forward a message to multiple consumers, this pattern is called publish-subscribe pattern.

  To illustrate this pattern, we will build a simple logging system that consists of two programs: one that sends log messages, and one that receives and prints log messages. In this logging system, each running consumer can get the message, in this case, we can achieve this requirement: one consumer receives the message and writes it to disk, and the other consumer receives the message and prints it on the on the computer screen. Simply put, the message published by the producer will be forwarded to all consumers in the form of broadcast.

1. Exchange

  In the first two chapters, we publish messages or get messages to the queue. However, the previous explanation is not complete. Next, it is time to introduce the complete RabbitMq message model.

  Recall what we covered in the first two chapters of our guide:

    • a producer to send messages;
    • a queue to cache messages;
    • A consumer is used to consume messages from the queue.

  The core idea of ​​the RabbitMq message pattern is that a producer does not send messages directly to a queue. In fact, the producer does not know which queues the messages it sends will be forwarded to.

  In fact, producers can only send messages to one exchange, and exchanges do one simple thing: on the one hand they receive messages from producers, and on the other hand, they push received messages to a queue. An exchage must know exactly what to do with a message.

  There are four types of exchanges, namely: direct, topic, headers, fanout. This chapter focuses on the last one: fanous (broadcast mode). Let's create an exchange of type fanout, let's call it :logs:

1 channel.exchangeDeclare("logs", "fanout");

  The broadcast mode exchange is very simple and can be understood literally. It actually pushes the received message to all the queues it knows about. This pattern is exactly what we need in our logging system.

  If you want to see how many exchanges are currently in the system, you can use the following command:

sudo rabbitmqctl list_exchanges

  Or view it through the console:

  You can see that there are many exchanges starting with amq.*, as well as (AMQP default) default exchanges, which are exchanges created by default.

   In the first two chapters of the guide, we didn't know about the existence of an exchange, but we could still send messages to the queue, not because we could not use an exchange, but because we used the default exchange ( Let's recall how we sent the message earlier by specifying the exchange as a literal string: ""):

1 channel.basicPublish("", "hello", null, message.getBytes());

  The first parameter is the name of the exchange, an empty string indicates that it is a default or unnamed exchange, and the message will be forwarded to the queue by the specified routing key (the second parameter, routingKey, will be described later).

  You may be wondering: since exchange can be specified as an empty string (""), can it be specified as null?

  The answer is: no!

  By tracking the code that publishes the message, in the Publish() aspect of the AMQImpl class, you can see that not only the exchange cannot be null, but also the routingKey routing key cannot be null, otherwise an exception will be thrown:

  Following the above explanation, we create a named exchange:

1 channel.basicPublish( "logs", "", null, message.getBytes());

2. Temporary queue

  In the examples in the first two chapters, the queues we used have specific queue names. It is necessary to create a named queue because we need to point consumers to a queue with the same name. Therefore, in order to share queues between producers and consumers, it is necessary to use named queues.

  However, the logging system explained in this chapter can also use unnamed queues (which may not be named manually), and we want to receive all log messages, not some. And we want to always receive new log messages instead of old ones. To solve this problem, two steps are required.

  First, whenever our consumer connects to RabbitMq, we need a new, empty queue to receive log messages, so the consumer needs to create a queue with any name after connecting to RabbitMq, or let RabbitMq generate any arbitrary name the queue name.

  Second, once the consumer disconnects from RabbitMq, the queue is also automatically deleted.

  Create a non-persistent, proprietary, auto-delete, and randomly-named queue through the JAVA client's no-argument method: queueDeclare().

1 String queueName = channel.queueDeclare().getQueue();

3. Binding

  The exchange and queue of the previous broadcast mode have been created, and the next step is to tell the exchange to send messages to the queue. The relationship between an exchange and a queue is called a binding relationship.

1 channel.queueBind(queueName, "logs", "");

  At this point, the exchange can send messages to the queue.

  You can view the binding relationship of the queue with the following commands:

4. Complete code

  EmitLog.java

 1 import com.rabbitmq.client.BuiltinExchangeType;
 2 import com.rabbitmq.client.Channel;
 3 import com.rabbitmq.client.Connection;
 4 import com.rabbitmq.client.ConnectionFactory;
 5 
 6 public class EmitLog {
 7 
 8     private static final String EXCHANGE_NAME = "logs";
 9 
10     public static void main(String[] args) throws Exception {
11 
12         ConnectionFactory factory = new ConnectionFactory();
13         factory.setHost("192.168.92.130");
14 
15         try (Connection connection = factory.newConnection();
16              Channel channel = connection.createChannel();) {
17 
18             channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
19 
20             String message = "RabbitMq fanout。。。。。。";
21             channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("utf-8"));
22 
23             System.out.println(" [x] Sent '" + message + "'");
24         }
25     }
26 }

  As you can see, after the Connection is created, the exchange is defined. This step is necessary because messages cannot be sent without an exchange.

  So no queue is bound to the exchange, then the messages received by the exchange will be lost, but it is no problem for our logging system in this chapter. When there are no consumers, we can safely discard the data, We only receive the latest log messages.

  ReceiveLogs.java

 1 public class ReceiveLogs {
 2 
 3     private static final String EXCHANGE_NAME = "logs";
 4 
 5     public static void main(String[] args) throws Exception {
 6 
 7         ConnectionFactory factory = new ConnectionFactory();
 8         factory.setHost("192.168.92.130");
 9 
10         Connection connection = factory.newConnection();
11         Channel channel = connection.createChannel();
12 
13         channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
14 
15         final String queue = channel.queueDeclare().getQueue();
16         channel.queueBind(queue,EXCHANGE_NAME,"");
17 
18         System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
19 
20         DeliverCallback deliverCallback = (consumerTa,delivery) -> {
21 
22             String message = new String(delivery.getBody(), "UTF-8");
23             System.out.println(" [x] Received '" + message + "'");
24 
25         };
26 
27         channel.basicConsume(queue,true,deliverCallback,consumerTag -> {});
28     }
29 }

  The autoAck here is set to true, because we are in broadcast mode here, each consumer will receive the same message, and the random name queue produced for the consumer here is equivalent to unique, so it is sent immediately after receiving the message Confirmation receipt is OK.

  But here is a question: In this mode, will the messages received by each queue also have Ready and Unacked states?

5. Test results

  First, start the producer first, then start two consumers

  It can be seen that the message sent by the producer after startup is lost, and the two consumers have not consumed it. At this time, look at the console:

  It can be seen that RabbitMq has created two randomly named queues for us. The Exclusive is Owner, which means it is exclusive, and the Parameters are AD (auto delete). Once the consumers who own the queue are disconnected, the queue will be automatically deleted. .

  Second, start the producer to send a message

  Both consumers received the message.

  3. Close all consumers and observe the console changes

  Two proprietary random queues were automatically deleted.

6. Implementation of SpringBoot

  Engineering structure diagram:

First, the configuration file application.properties:

  Producer:

#RabbitMq
spring.rabbitmq.host=192.168.92.130
spring.rabbitmq.exchange=logs

  consumer:

#RabbitMq
spring.rabbitmq.host=192.168.92.130
spring.rabbitmq.exchange=logs

##队列--我们可以自己指定队列名称,也可以由RabbitMq自动生成,这里为了方便,我们自己命名(如果需要,我也可以写一个自动生成名称的方法)
rqbbitmq.log.fanout.info=info
rqbbitmq.log.fanout.error=error
server.port=8090

2. Producer code

  Here, in order to automatically send a message when the system producer starts, I have added an EmitLogRunner class.

  EmitLog.java

 1 import org.springframework.amqp.core.AmqpTemplate;
 2 import org.springframework.beans.factory.annotation.Autowired;
 3 import org.springframework.beans.factory.annotation.Value;
 4 import org.springframework.stereotype.Component;
 5 
 6 @Component
 7 public class EmitLog {
 8 
 9     @Value("${spring.rabbitmq.exchange}")
10     private String exchange;
11 
12     @Autowired
13     private AmqpTemplate amqpTemplate;
14 
15     public void send(String msg) {
16         amqpTemplate.convertAndSend(exchange,"",msg);
17     }
18 }

  EmitLogRunner.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class EmitLogRunner implements ApplicationRunner {

    @Autowired
    private EmitLog emitLog;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("生产者发布消息:" + msg);
        emitLog.send("RabbitMq fanout test message");
    }
}

2. Consumer code  

  ReceiveInfoLogs.java

@Component
@RabbitListener(
        bindings = @QueueBinding(
                value = @Queue(value = "${rqbbitmq.log.fanout.info}",autoDelete = "true"),
                exchange = @Exchange(value = "${spring.rabbitmq.exchange}",type = ExchangeTypes.FANOUT)
        )
)
public class ReceiveInfoLogs {

    @Autowired
    private AmqpTemplate amqpTemplate;

    @RabbitHandler
    public void receiveInfoLog (Object message) {
 
        System.out.println("接收到info级别的日志:" + message);
    }
}

  ReceiveErrorLogs.java

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(
        bindings = @QueueBinding(
                value = @Queue(value = "${rqbbitmq.log.fanout.error}",autoDelete = "true"),
                exchange = @Exchange(value = "${spring.rabbitmq.exchange}",type = ExchangeTypes.FANOUT)
        )
)
public class ReceiveErrorLogs {

    @Autowired
    private AmqpTemplate amqpTemplate;

    @RabbitHandler
    public void receiveErrorLog(Object message) {
        System.out.println("接收到的error级别日志:" + message);
    }
}

  Note that the annotations in the bindings start with @ and add the corresponding items to be bound. After thinking about it, you should be able to understand.

3. Verification

 Start the consumer and producer, and view the console:

Pay attention, don't get lost, this is a public account that programmers want to pay attention to

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324134145&siteId=291194637