(8)RabbitMQ之topic(主题)交换机

本节将会介绍RabbitMQ中的topic(主题)交换机,通过在上一篇博客的基础上,利用topic形式的exchange对程序进行改进,将会变得更加灵活。

Topic exchange(主题交换机)

发送到topic交换机的消息不能具有任意的  routing_key —— 它必须是由点分隔的单词列表。单词可以是任何内容,但通常它们指定与消息相关的一些功能。一些有效的路由键示例:“ stock.usd.nyse ”,“ nyse.vmw ”,“ quick.orange.rabbit ”。其中可以包含任意数量的单词,最多可达255个字节。

binding key也必须采用相同的形式。topic交换机背后的逻辑 类似于direct交换机——使用特定 routing key 发送的消息将被传递到与匹配binding key绑定的所有队列。但是binding key有两个重要的特殊情况:

  1. *(星号)可以替代一个单词。
  2. #(hash)可以替换零个或多个单词。

在一个例子中解释这个是最容易的:

(8)RabbitMQ之topic(主题)交换机_第1张图片

我们准备发送关于动物的消息。消息会附加一个选择键包含3个标识符(两个点隔开)。第一个标识符描述动物的速度,第二个标识符描述动物的颜色,第三个标识符描述动物的物种:..

我们创建3个绑定键:Q1与*.orange.*绑定Q2与*.*.rabbit和lazy.#绑定。可以概括为:

  • Q1对所有的橙色动物感兴趣。

  • Q2想要知道关于兔子的一切以及关于懒洋洋的动物的一切。

一个附带quick.orange.rabbit的选择键的消息将会被转发到两个队列。附带lazy.orange.elephant的消息也会被转发到两个队列。另一方面quick.orange.fox只会被转发到Q1,lazy.brown.fox将会被转发到Q2。lazy.pink.rabbit虽然与两个绑定键匹配,但是也只会被转发到Q2一次。quick.brown.fox不能与任何绑定键匹配,所以会被丢弃。

如果我们违法我们的约定,发送一个或者四个标识符的选择键,类似:orange,quick.orange.male.rabbit,这些选择键不能与任何绑定键匹配,所以消息将会被丢弃。

另一方面,lazy.orange.male.rabbit,虽然是四个标识符,也可以与lazy.#匹配,从而转发至Q2。

注意:主题类型的转发器非常强大,可以实现其他类型的转发器。

当一个队列与绑定键#绑定,将会收到所有的消息,类似fanout类型转发器。

当绑定键中不包含任何#与*时,类似direct类型转发器。

代码示例

生产者的routing key会从数组{ "order.info", "order.warn.middle", "order.error.high" ,"user.info" }中随机选择一个,消费者1的binding key为"*.info",则如果发送的消息的routing key 以一个单词开头以info结尾(中间以"."分隔)会路由到消费者1;消费者2的binding key为"order.#",则如果发送的消息的routing key 如果以order开头都会路由到消费者2。

生产者代码如下:项目GitHub地址 https://github.com/RookieMember/RabbitMQ-Learning.git。

package cn.wkp.rabbitmq.newest.exchange.topic;

import java.util.Random;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import cn.wkp.rabbitmq.util.ConnectionUtil;

public class Send {

	private final static String EXCHANGE_NAME = "topic_exchange";

	private static String[] logLevels = { "order.info", "order.warn.middle", "order.error.high" ,"user.info" };

	public static void main(String[] argv) throws Exception {
		// 获取到连接以及mq通道
		Connection connection = ConnectionUtil.getConnection();
		// 从连接中创建通道
		Channel channel = connection.createChannel();

		// 声明交换机
		channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

		Random random = new Random();
		// 消息内容
		for (int i = 0; i < 10; i++) {
			// 随机得到路由键
			String routingKey = logLevels[random.nextInt(logLevels.length)];
			String message = "日志序号i:" + i + ",级别:" + routingKey;
			channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
			System.out.println("Sent message:" + message);
		}
		// 关闭通道和连接
		channel.close();
		connection.close();
	}
}

消费者1代码如下:

package cn.wkp.rabbitmq.newest.exchange.topic;

import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import cn.wkp.rabbitmq.util.ConnectionUtil;

public class Recv1 {

	private final static String EXCHANGE_NAME = "topic_exchange";
	private final static String QUEUE_NAME = "topic_queue1";

	public static void main(String[] argv) throws Exception {

		// 获取到连接以及mq通道
		Connection connection = ConnectionUtil.getConnection();
		final Channel channel = connection.createChannel();

		// 声明交换机
		channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
		// 声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);

		// 绑定队列到交换机 *可以代替一个单词
		channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "*.info");

		// 指该消费者在接收到队列里的消息但没有返回确认结果之前,它不会将新的消息分发给它。
		channel.basicQos(1);

		// 定义队列的消费者
		Consumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费者1收到消息:" + new String(body));
				// 消费者手动发送ack应答
				channel.basicAck(envelope.getDeliveryTag(), false);
			}
		};
		// 监听队列
		channel.basicConsume(QUEUE_NAME, false, consumer);
	}
}

消费者2代码如下:

package cn.wkp.rabbitmq.newest.exchange.topic;

import java.io.IOException;

import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import cn.wkp.rabbitmq.util.ConnectionUtil;

public class Recv2 {

	private final static String EXCHANGE_NAME = "topic_exchange";
	private final static String QUEUE_NAME = "topic_queue2";

	public static void main(String[] argv) throws Exception {

		// 获取到连接以及mq通道
		Connection connection = ConnectionUtil.getConnection();
		final Channel channel = connection.createChannel();

		// 声明交换机
		channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
		// 声明队列
		channel.queueDeclare(QUEUE_NAME, false, false, false, null);

		// 绑定队列到交换机  #可以代表0个或多个单词
		channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "order.#");

		// 指该消费者在接收到队列里的消息但没有返回确认结果之前,它不会将新的消息分发给它。
		channel.basicQos(1);

		// 定义队列的消费者
		Consumer consumer = new DefaultConsumer(channel) {
			@Override
			public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
					throws IOException {
				System.out.println("消费者2收到消息:" + new String(body));
				// 消费者手动发送ack应答
				channel.basicAck(envelope.getDeliveryTag(), false);
			}
		};
		// 监听队列
		channel.basicConsume(QUEUE_NAME, false, consumer);
	}
}

运行结果如下(因为routing key是随机的,所以每次运行结果会不一样):

Sent message:日志序号i:0,级别:order.error.high
Sent message:日志序号i:1,级别:user.info
Sent message:日志序号i:2,级别:user.info
Sent message:日志序号i:3,级别:order.error.high
Sent message:日志序号i:4,级别:order.info
Sent message:日志序号i:5,级别:order.warn.middle
Sent message:日志序号i:6,级别:order.warn.middle
Sent message:日志序号i:7,级别:order.info
Sent message:日志序号i:8,级别:order.warn.middle
Sent message:日志序号i:9,级别:order.info
消费者1收到消息:日志序号i:1,级别:user.info
消费者1收到消息:日志序号i:2,级别:user.info
消费者1收到消息:日志序号i:4,级别:order.info
消费者1收到消息:日志序号i:7,级别:order.info
消费者1收到消息:日志序号i:9,级别:order.info
消费者2收到消息:日志序号i:0,级别:order.error.high
消费者2收到消息:日志序号i:3,级别:order.error.high
消费者2收到消息:日志序号i:4,级别:order.info
消费者2收到消息:日志序号i:5,级别:order.warn.middle
消费者2收到消息:日志序号i:6,级别:order.warn.middle
消费者2收到消息:日志序号i:7,级别:order.info
消费者2收到消息:日志序号i:8,级别:order.warn.middle
消费者2收到消息:日志序号i:9,级别:order.info

 

你可能感兴趣的:(RabbitMQ)