3 Publish/Subscribe


Official document address: 3 Publish/Subscribe


Send messages to many consumers at the same time.

Prerequisites

This tutorial assumes that you have installed RabbitMQ and is running on the local host port (5672).

Publish/Subscribe

In the previous tutorial, we created a work queue. The assumption behind the work queue is that each task is only delivered to one worker. In this part, we will do something completely different-we will deliver a message to multiple consumers. This model is called "publish/subscribe".

To demonstrate this pattern, we will build a simple logging system. It will consist of two programs-the first program will issue log messages, and the second program will receive and print messages.

In our log system, every running copy of the consumer program will get a message. In this way, we can run a consumer to direct the log to disk; at the same time, we can run another consumer to print the log to the console.

In fact, the published log message will be broadcast to all receivers.

Switch

In the previous tutorial, we just sent and received messages to the queue. Now it is time to introduce a complete messaging model in RabbitMQ.

Let's quickly review what was covered in the previous tutorial:

  • The producer is the user application that sends the message.
  • The queue is a buffer for storing messages.
  • Consumers are user applications that receive messages.

The core idea of ​​the RabbitMQ messaging model is that the producer never sends any messages directly to the queue. In fact, usually the producer does not even know which queue the message was delivered to.

Instead, the producer can only exchangesend messages to. Exchange is a very simple matter. On the one hand, it receives messages from the producer, on the other hand, it pushes the messages to the queue. exchangeIt must be clear how to deal with the received message. Should it be sent to a specific queue? Should it be sent to many queues? Or it should be discarded. These rules are exchangedefined by types.

There are several types of directswitches: topic, headers, fanout, . This tutorial will use the last one- fanout. Let's create one of this type exchangeand name it logs:

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

fanoutThe switch is very simple. It just broadcasts all the messages it receives to all the queues it knows about. This is exactly what our recorder needs.

Exchange List

To list the exchange list on the server, you can run rabbitmqctl:

sudo rabbitmqctl list_exchanges

On Windows:

rabbitmqctl.bat list_exchanges

There will be some amq.*switches and default switches (unnamed) in this list . These are created by default, but you are unlikely to need to use them now.

The default exchange

In the previous tutorial, we did exchangen't know anything about it, but we were still able to send messages to the queue. This is because we are using the default exchange, and we use an empty string ( "") to identify it. Recall the code of the message we posted earlier:

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

The first parameter is exchangethe name. An empty string indicates the default exchange: the message is routed to routingKeythe queue specified by the name, if it exists.

Now, we can publish the message to the exchange we named. Note that the queue name parameter is empty at this time:

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

Temporary queue

You may remember that we used queues with specific names before (remember helloand task_queue?). Being able to name a queue is crucial for us-when we need to workerpoint to the same queue, or want to share the queue between the producer and the consumer, it is very important to give the queue a name.

But for our recorder, this is not the case. We want to understand all log messages, not just a subset of them. We are also only interested in the current flow of news, and not interested in the old news. To solve this problem, we need to do two things.

First, whenever we connect to RabbitMQ, we need a new empty queue. To do this, we can create a queue with a random name, or, a better way is to let the server choose a random queue name for us.

Secondly, once we disconnect the consumer, the queue should be automatically deleted.

In the Java client, when we don't queueDeclare()provide any parameters, we will create a non-persistent, exclusive, automatically deleted queue with a randomly generated name.

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

You can learn more about exclusive flags and other queue attributes in the queue guideexclusive .

At this time, it queueNameis a random queue name. For example, it might look like amq.gen-JzTY20BRgKO-HjmUJj0wLg.

Bind


We have created a fanoutswitch and a queue. Now we need to tell the exchange to send the message to our queue. The relationship between the switch and the queue is called binding binding.

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

From now on, the logsexchange will append messages to our queue.

The binding list

can list the existing bindings:

rabbitmqctl list_bindings

On Windows:

rabbitmqctl.bat list_bindings

Put them together


The producer program sends out log messages, which is not much different from the previous tutorial. The most important change is that we now want to publish messages to the logsexchange instead of the default exchange. We need to provide one when sending routingKey, but the fanoutexchange will ignore it (so it will not provide it).

EmitLog.javaThe code of the class:

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author wangbo
 * @date 2019/10/23 11:24
 */
public class EmitLog {
    
    
    //交换器名称
    private final static String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //创建一个连接器连接到服务器
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try(Connection connection = factory.newConnection()){
    
    
            //创建一个通道
            Channel channel = connection.createChannel();
            //声明交换器,设置交换器类型 fanout
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
            //从命令行接受参数
            String message = args.length < 1 ? "info: Hello World!" : String.join(" ", args);
            //发布消息到交换器
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

As you can see, after establishing the connection, we declared exchange. This step is necessary because publishing to non-existent exchanges is prohibited.

If there is no queue bound to it exchange, the message will be lost, but this is no problem for us; if there is no consumer listening, we can safely discard the information.

ReceiveLogs.javaThe code of the class:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author wangbo
 * @date 2019/10/22 18:25
 */
public class ReceiveLogs {
    
    
    //交换器名称
    private final static String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //创建一个连接器连接到服务器
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        //创建一个通道
        Channel channel = connection.createChannel();
        //声明交换器,设置交换器类型 fanout
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        //获取临时队列的名称
        String queueName = channel.queueDeclare().getQueue();
        //将临时队列和交换器绑定
        channel.queueBind(queueName, EXCHANGE_NAME, "");
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
        //回调对象
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
    
    
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" + message + "'");
        };
        //消费者监听
        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
    
    });
    }
}

Compile, for convenience, we will use environment variables for the classpath $CP(on Windows %CP%):

javac -cp $CP EmitLog.java ReceiveLogs.java

If you want to save the log to a file, just open the console and run the consumer:

java -cp $CP ReceiveLogs > logs_from_rabbit.log

If you want to see the log on your screen, open a new terminal and run the consumer:

java -cp $CP ReceiveLogs

Run the producer that generates the log:

java -cp $CP EmitLog

Using it rabbitmqctl list_bindings, you can verify whether the code actually creates the binding and queue we want. When you run the two ReceiveLogs.javaprograms, you should see the following:

sudo rabbitmqctl list_bindings
# => Listing bindings ...
# => logs    exchange        amq.gen-JzTY20BRgKO-HjmUJj0wLg  queue           []
# => logs    exchange        amq.gen-vso0PVvyiRIL2WoV3i48Yg  queue           []
# => ...done.

The interpretation of the results is simple: logsthe data from the switch enters two queues with server assigned names. This is exactly what we want.

Guess you like

Origin blog.csdn.net/wb1046329430/article/details/115281608