系统拆分解耦利器之消息队列---RabbitMQ-主题(Topic)

来源:互联网 发布:上海程序员培训机构 编辑:程序博客网 时间:2024/05/19 00:14

[一曲广陵不如晨钟暮鼓]

本文,我们来介绍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为“quick.orange.rabbit”,其将会被投递到两个队列中。如果发送的消息使用的binding key为"lazy.orange.elephant"同样将会被投递到两个队列当中。但是如果发送的消息使用的binding key为“quick.orange.fox”将会只投递到Q1的队列中。同理,如果发送的消息使用的binding key为“lazy.brown.fox”,那其就只会被投递到Q2当中。特别的,如果消息使用的binding key为“lazy.pink.rabbit”,正如上面我们介绍的“#”的规则定义,其将会被投递到Q2队列当中,并且只会被投递一次。最后,像前面的direct一样,如果消息的binding key没有与之配置的队列,如“quick.brown,fox”,其将会被丢弃掉。
特别的,如果发送了一个只包含一个单词的binding key或者单词数量更多的binding key时,又作如何处理呢?如:“orange”,“quick.orange.male.rabbit”。如果是这样的话,由于其没有匹配到任何规则,所以都会被丢弃掉。另一方面,如果匹配到了,如“lazy.orange.male.rabbit”,即使其包含有4个单词,它也是能够被投递到Q2当中去的。
特别备注:
RabbitMQ的Topic模式的功能是非常强大的。它能够表现成为其他任何一个类型的exchange。
  • 当一个队列的binding key为“#”时,其能够接收到所有的消息,就像routing key不存在一样。
  • 当一个队列的binding key中不使用“#”或者“*”时,其表现出的特性与direct类型表现的特性完全一致。
综上所述,我们来看看完整的工程吧,结构图如下:

1.修改pom文件,具体内容请看前文,在此不再赘述。
2.创建EmitLogTopic文件,具体内容如下:
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

1 0
原创粉丝点击