[一曲广陵不如晨钟暮鼓]
本文,我们来介绍RabbitMQ中的Topic类型的exchange。在正式开始之前,我们假设RabbitMQ服务已经启动,运行端口为5672,如果各位看官有更改过默认配置,那么就需要修改为对应端口,保持一致即可。
准备工作:
操作系统:window 7 x64
其他软件:eclipse mars,jdk7,maven 3
--------------------------------------------------------------------------------------------------------------------------------------------------------
主题(Topic)
在上文中,我们已经使用点对点的direct方式替换掉广播式的fanout模式,从而改进了日志系统。尽管这种点对点的direct方式已经成功的提升了系统的灵活性,但是其还是有限制的。那就是其不能基于多参数的路由。
在我们构建的日志系统中,我们想要订阅的方式不只是基于日志级别,并且还需要按照日志的生产源进行订阅。你可能从unix日志工具:syslog当中已经了解到这个改变,这种路由方式同时基于日志的级别和产生日志的生产源(auth/cron/kern)。
通过这种方式,能够给我们收集系统日志提供极大的灵活性。举个例子:比如我们想监听的只是非常严重的错误,并且错误源是“cron”,在这个基础之上,我们还需要收集来自“kern”的所有日志。
为了实现上面的这个需求,下面来介绍一下RabbitMQ中更加复杂的toipc类型的exchange的概念及使用方法吧。
主题类型的交换器(Topic exchange)
在向topic类型的exchange发送消息时,不能够随意的设定routing_key。其必须是一个通过句号分割的单词的列表。至于单词,可以是任意的有意义的或者无意义的均,但通常情况下,这个单词都暗示了消息的某些特性。举个例子:“stockl.usd.nyse”,“nyse.vmw”,“quick.orange.rabbit”等等。单词格式不做任何的限制,但是其长度最多只能有255个字节(byte)。
同时,binding key必须是与消息发送者使用的binding key的格式保持一致。这里topic后台处理逻辑和前文我们介绍的点对点的direct处理逻辑非常类似。一个带有特定binding key的消息将会被投递到所有的与之匹配的队列当中。但是,请注意以下两个特殊符号:
- *(star):只能匹配一个单词。(0个或者多个都不可以)
- #(hash):能够匹配大于等于零个单词。
这个概念可以很容易的使用下面这张图进行解释:
在这幅图中,我们将会发送能够描述动物特性的消息。消息将带有一个routing key(有两个间隔点)。第一个单词用以描述速度。第二个单词用以描述颜色。第三个单词用以描述种类。合起来的格式就是:“<speed>.<colour>.<species>”。
我们创建三个绑定关系:
- Q1:【*orange.*】
- Q2:【*.*.rabbit】,【lazy.#】
- 当一个队列的binding key为“#”时,其能够接收到所有的消息,就像routing key不存在一样。
- 当一个队列的binding key中不使用“#”或者“*”时,其表现出的特性与direct类型表现的特性完全一致。
package com.csdn.ingo.rabbitmq_1;
import java.io.IOException;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class EmitLogTopic {
// 队列名称
private final static String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException {
// 创建连接和频道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 声明队列
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String[] routing_keys = new String[] { "kernel.info", "cron.warning", "auth.info", "kernel.critical" };
for (String routing_key : routing_keys) {
String message = UUID.randomUUID().toString();
channel.basicPublish(EXCHANGE_NAME, routing_key, null, message.getBytes());
System.out.println("[x] Sent routing_key:" + routing_key + ",message:" + message);
}
// 关闭频道和资源
channel.close();
connection.close();
}
}
3.创建ReceiveLogsTopicForCritical文件,具体内容如下:
package com.csdn.ingo.rabbitmq_1;
import java.io.IOException;
import java.util.Random;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException;
public class ReceiveLogsTopicForCritical {
private final static String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args)
throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "*.critical");
System.out.println("[*] waiting for messages. To exit press CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
String routingkey = delivery.getEnvelope().getRoutingKey();
System.out.println("[x] Received routingKey:" +routingkey+",message:"+ message);
}
}
}
4.创建ReceiveLogsTopicForKernel文件,具体内容如下:
package com.csdn.ingo.rabbitmq_1;
import java.io.IOException;
import java.util.Random;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException;
public class ReceiveLogsTopicForKernel {
private final static String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args)
throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "kernel.*");
System.out.println("[*] waiting for messages. To exit press CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
String routingkey = delivery.getEnvelope().getRoutingKey();
System.out.println("[x] Received routingKey:" +routingkey+",message:"+ message);
}
}
}
5.测试方法:首先运行两个接收方程序,在运行发送者程序,观察控制台输出即可。
-------------------------------------------------------------------------------------------------------------------------------------------------------
至此,系统拆分解耦利器之消息队列---RabbitMQ-主题(Topic) 结束
参考资料:
官方文档:http://www.rabbitmq.com/tutorials/tutorial-five-java.html