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

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

本文,我们来介绍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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值