Routing(按指定路线发送)
在之前的笔记中,我们构建了一个简单的日志记录系统,能够同时向许多接收器发送日志消息。
在本次笔记中,我们将为它添加一个新特性——我们将实现只订阅消息的一个子集。例如,我们只把关键错误消息(error)直接导向日志文件(以保存磁盘空间),同时仍然能够在控制台上打印出所有日志消息(包括error和warning,info)。
绑定
在前面的示例中,我们已经创建了绑定。你可能会记得:
channel.queueBind(queueName, EXCHANGE_NAME, "");
绑定是交换器(exchange)和队列(queue)之间的关系。这可以简单地看为成:队列对来自交换器的消息感兴趣。
绑定可以采用额外的参数routingKey。为了避免与basic_publish的参数混淆,我们将把它称为 binding key(绑定键)。这就是我们如何创建一个binding key:
channel.queueBind(queueName, EXCHANGE_NAME, "black");
binding key的含义取决于交换器类型。我们之前使用的fanout交换器,简单地忽略掉它的值。
Direct 交换器
我们上一个教程中的日志系统向所有用户广播所有的消息。我们希望扩展该功能,允许基于它们的性质来过滤消息。例如,我们可能需要一个将日志消息写入到磁盘的程序,它只接收关键错误(error),而不将警告或信息日志消息(warning ,info)写入而浪费磁盘空间。
之前,我们使用的是一个 fanout 交换器,这并没有给我们很大的灵活性——它只会无意识,无区分地广播消息。
现在,我们将使用 direct 交换器。direct交换器背后的路线指定算法很简单——一条消息,若它的 routing key 与某一队列的 binding key 完全匹配,则将被传递给该队列。
为了说明这一点,请参考下面的设置:
在这个设置中,我们可以看到与它绑定的两个队列的 direct 交换器 X 。第一个队列与 binding key 橙色绑定,第二个队列有两个绑定,一个是 binding key 黑色,另一个是绿色。
在这样的设置中,发送到交换器的消息中,带有橙色 routing key 的消息将被发送到队列 Q1 。带有黑色或绿色 routing key 的消息将被发送到 Q2 。所有其他所有的信息将被丢弃。
多个绑定
用相同的 binding key 绑定多个队列是完全合法的。在我们的示例中,我们可以在 X 和 Q1 之间添加 binding key 黑色。在这种情况下,direct 交换器将表现得像 fanout 交换器,并将消息广播到所有匹配的队列。带有黑色 routing key 的消息将被发送到 Q1 和 Q2 。
Emitting logs(发送日志)
我们将为我们的日志系统使用这个模型。我们不会向外发信息,而是将信息发送到 direct 交换器。我们将提供日志性质(error, warning, info)作为 routing key。这样,接收程序就能够选择它想要接收的性质。首先,让我们把注意力放在发送日志上。
一如既往,我们需要先创建一个交换器:
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
我们准备发出一个信息:
channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());
Subscribing(订阅消息)
接收消息的代码将像之前的笔记一样,有一个不同——我们将为我们感兴趣的每个消息性质创建一个新的绑定。
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.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
public class EmitLogDirect {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String severity = getSeverity(argv);
String message = getMessage(argv);
channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + severity + "':'" + message + "'");
channel.close();
connection.close();
}
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;
public class ReceiveLogsDirect {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] argv) throws Exception {
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 (argv.length < 1){
System.err.println("Usage: ReceiveLogsDirect [info] [warning] [error]");
System.exit(1);
}
for(String severity : argv){
channel.queueBind(queueName, EXCHANGE_NAME, severity);
}
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'");
}
};
channel.basicConsume(queueName, true, consumer);
}
}
像往常一样编译运行(请参阅学习笔记一中编译注意事项和类路径建议)。为了方便起见,在运行示例时,我们将使用环境变量$ CP。
javac -cp $CP ReceiveLogsDirect.java EmitLogDirect.java
如果你只想保存“error”和“warning”(而不是“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.’
相关链接
rabbitmq-c++(SimpleAmqpClient) 笔记代码系列:
rabbitmq-c++(SimpleAmqpClient) 笔记代码一
rabbitmq-c++(SimpleAmqpClient) 笔记代码二
rabbitmq-c++(SimpleAmqpClient) 笔记代码三
rabbitmq-c++(SimpleAmqpClient) 笔记代码四
rabbitmq-c++(SimpleAmqpClient) 笔记代码五
rabbitmq-c++(SimpleAmqpClient) 笔记代码六
RabbitMQ 学习笔记系列:
RabbitMQ 学习笔记(一):简单介绍及”Hello World”
RabbitMQ 学习笔记(二):work queues
RabbitMQ 学习笔记(三):Publish/Subscribe
RabbitMQ 学习笔记(四):Routing
RabbitMQ 学习笔记(五):Topics
RabbitMQ 学习笔记(六):RPC