Article Directory
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 exchange
send 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. exchange
It 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 exchange
defined by types.
There are several types of direct
switches: topic
, headers
, fanout
, . This tutorial will use the last one- fanout
. Let's create one of this type exchange
and name it logs
:
channel.exchangeDeclare("logs", "fanout");
fanout
The 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 runrabbitmqctl
:
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 didexchange
n'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
exchange
the name. An empty string indicates the default exchange: the message is routed toroutingKey
the 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 hello
and task_queue
?). Being able to name a queue is crucial for us-when we need to worker
point 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 queueName
is a random queue name. For example, it might look like amq.gen-JzTY20BRgKO-HjmUJj0wLg
.
Bind
We have created a fanout
switch 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 logs
exchange 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 logs
exchange instead of the default exchange. We need to provide one when sending routingKey
, but the fanout
exchange will ignore it (so it will not provide it).
EmitLog.java
The 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.java
The 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.java
programs, 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: logs
the data from the switch enters two queues with server assigned names. This is exactly what we want.