官方文档地址: 4 Routing
有选择地接收消息。
前提条件
本教程假设你已经安装了 RabbitMQ 并在本地主机端口(5672)上运行。
路由
在前一篇教程中,我们构建了一个简单的日志系统。我们能够向许多接收者广播日志消息。
在本教程中,我们将为其添加一个特性 - 仅订阅消息的一个子集。例如,我们仅将关键的错误消息定向到日志文件(以节省磁盘空间),同时仍然能够在控制台上打印所有的日志消息。
绑定
在前面的示例中,我们已经创建了绑定,回忆代码如下:
channel.queueBind(queueName, EXCHANGE_NAME, "");
绑定是交换器和队列之间的关系。这可以简单地理解为:队列对来自这个交换器的消息感兴趣。
绑定可以使用额外的routingKey
参数。为了避免与basic_publish
参数混淆,我们将其称为binding key
。我们创建一个绑定键:
channel.queueBind(queueName, EXCHANGE_NAME, "black");
绑定键的作用取决于exchange
类型。我们之前使用的fanout
交换器会忽略了它的值。
Direct 交换器
之前教程中的日志系统将消息广播给所有用户。我们希望对其进行扩展,允许根据消息的严重程度过滤消息。例如,我们可能希望只将error
级别的日志消息写入磁盘,而不会将warning
或info
日志消息写入磁盘。
我们使用的是fanout
交换器,它没有给我们太多的灵活性 - 只能进行盲目的广播。
我们将用direct
交换器代替它。direct
交换器背后的路由算法很简单 - 消息将进入绑定键binding key
与消息的路由键routing key
完全匹配的队列。
为了说明这一点,请考虑以下设置:
在这个设置中,我们可以看到direct
交换器X
绑定了两个队列。第一个队列用绑定键orange
绑定,第二个队列有两个绑定,一个绑定键是black
,另一个是green
。
在这样的设置中,使用路由键orange
发布到交换器的消息将被路由到队列Q1
。带有black
或green
路由键的消息将转到Q2
。其他消息将被丢弃。
多重绑定
使用相同的绑定键绑定多个队列是完全合法的。在我们的示例中,可以在X
和Q1
之间添加一个绑定键black
。在这种情况下,direct
交换器将像fanout
交换器一样工作,将消息广播到所有匹配的队列。带有路由键black
的消息将被发送到Q1
和Q2
。
发出日志
我们将在日志系统中使用这个模型。我们将发送消息到一个direct
交换器,而不是发送到fanout
交换器。我们将提供日志严重性作为routing key
。这样,接收程序将能够根据严重性有选择的接收日志。让我们首先来看一下发出日志的代码。
和往常一样,我们需要先创建一个交换器:
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
我们已经准备好发送一个信息:
channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());
为了简化问题,我们假设日志级别severity
可以是info
、warning
、error
。
订阅
接收消息的工作方式与前面的教程一样,但有一个例外 - 我们将为感兴趣的每个严重性创建一个新的绑定。
String queueName = channel.queueDeclare().getQueue();
for(String severity : argv){
channel.queueBind(queueName, EXCHANGE_NAME, severity);
}
把它们放一起
EmitLogDirect.java
类代码:
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author wangbo
* @date 2019/10/23 11:24
*/
public class EmitLogDirect {
//交换器名称
private final static String EXCHANGE_NAME = "direct_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();
//声明交换器,设置交换器类型
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//从命令行接受参数
//日志级别,路由键
String severity = getSeverity(args);
//日志消息
String message = getMessage(args);
//发布消息到交换器,并设置路由键
channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "'");
}
}
private static String getSeverity(String[] strings) {
if (strings.length < 1) {
return "info";
}
return strings[0];
}
private static String getMessage(String[] strings) {
if (strings.length < 2) {
return "Hello World!";
}
return joinStrings(strings, " ", 1);
}
private static String joinStrings(String[] strings, String delimiter, int startIndex) {
int length = strings.length;
if (length == 0) {
return "";
}
if (length <= startIndex) {
return "";
}
StringBuilder words = new StringBuilder(strings[startIndex]);
for (int i = startIndex + 1; i < length; i++) {
words.append(delimiter).append(strings[i]);
}
return words.toString();
}
}
ReceiveLogsDirect.java
类代码:
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author wangbo
* @date 2019/10/22 18:25
*/
public class ReceiveLogsDirect {
//交换器名称
private final static String EXCHANGE_NAME = "direct_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();
//声明交换器,设置交换器类型
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//获取临时队列的名称
String queueName = channel.queueDeclare().getQueue();
//将临时队列和交换器绑定,进行多重绑定
if (args.length < 1) {
System.err.println("Usage: ReceiveLogsDirect [info] [warning] [error]");
System.exit(1);
}
for (String severity : args) {
channel.queueBind(queueName, EXCHANGE_NAME, severity);
}
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 -> {
});
}
}
照常编译(有关编译和类路径建议,请参阅教程1)。为了方便,在运行示例时,我们将对类路径使用环境变量$CP
(即Windows
上的%CP%
)。
javac -cp $CP ReceiveLogsDirect.java EmitLogDirect.java
如果你只想保存warning
和error
(而不是info
)日志信息到一个文件,只要打开控制台并键入:
java -cp $CP ReceiveLogsDirect warning error > logs_from_rabbit.log
如果你想在你的屏幕上看到所有的日志信息,打开一个新的终端:
java -cp $CP ReceiveLogsDirect info warning error
# => [*] Waiting for logs. To exit press CTRL+C
例如,要发出error
日志消息,只需输入:
java -cp $CP EmitLogDirect error "Run. Run. Or it will explode."
# => [x] Sent 'error':'Run. Run. Or it will explode.'
请继续阅读第5
课,了解如何根据模式侦听消息。