RabbitMQ进阶-Queue队列特性 (三) 发布/订阅 模式(3)-主题交换机
文章目录
1.发布/订阅 模式
本篇文章紧接 发布订阅模式前篇 RabbitMQ系列(五)RabbitMQ进阶-Queue队列特性 (三) 发布/订阅 模式(1)
本片文章接着讲 发布订阅模式的另外几种交换机
2.Direct 直连交换机
在上篇文章中讲过 RabbitMQ系列(五)RabbitMQ进阶-Queue队列特性 (三) 发布/订阅 模式(1)-直连交换机
3.Fanout 扇形交换机
在之前文章讲过 RabbitMQ系列(六)RabbitMQ进阶-Queue队列特性 (三) 发布/订阅 模式(2)-扇形交换机
4.Topic 主题交换机
发送到主题交换机(topic exchange)的消息有固定格式的路由键(Routingkey),它的路由键必须是一个由.分隔开的词语列表。这些单词随便是什么都可以,但是最好是跟携带它们的消息有关系的词汇。以下是几个推荐的例子:“rk.company.*”, " *.*.employee","#.employee.#", “#.employee”。词语的个数可以随意,但是不要超过255字节。
绑定键也必须拥有同样的格式。主题交换机背后的逻辑跟直连交换机很相似 —— 一个携带着特定路由键的消息会被主题交换机投递给绑定键与之想匹配的队列。但是它的绑定键和路由键有两个特殊模糊匹配方式:
- (星号 *) 用来表示一个单词.
- (井号#) 用来表示任意数量(零个或多个)单词
下面我们解释一下这几个队列的Key,然后坐下实验,看看是否是我们猜想的这样
- “rk.company” 表示以精确 rk.company 的队列的Key 分发到A这里
- “rk.company.*” 表示以 rk.company开头,后面跟1个单词的队列的Key 分发到B这里
- " *.*.employee" 表示以两个词开头的 结尾是employee的队列的 Key 分发到C这里
- ,"#.employee.#" 表示以任意(个数)词开头,以任意(个数)词结尾 中间包含 employee的 Key分发到D这里
- “#.employee” 表示以任意(个数)词开头,以employee结尾的Key队列分发到E这里
4.1 代码实战
这次我们不创建消费者,直接通过队列的消息数来看,消息经过Topic交换机后,到底如何分发
先创建包,新建包topic,包下面新建交换机枚举ExchangeTypeEnum
package fanout;
public enum ExchangeTypeEnum {
DIRECT("exchange-direct-name", "direct"),
FANOUT("exchange-fanout-name", "fanout"),
TOPIC("exchange-topic-name", "topic"),
HEADER("exchange-header-name", "headers"),
UNKNOWN("unknown-exchange-name", "direct");
/**
* 交换机名字
*/
private String name;
/**
* 交换机类型
*/
private String type;
ExchangeTypeEnum(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
public static ExchangeTypeEnum getEnum(String type) {
ExchangeTypeEnum[] exchangeArrays = ExchangeTypeEnum.values();
for (ExchangeTypeEnum exchange : exchangeArrays) {
if (exchange.getName().equals(type)) {
return exchange;
}
}
return ExchangeTypeEnum.UNKNOWN;
}
}
创建主题交换机常量类,定义一些 RK及队列
package topic;
public class TopicConst {
/**
* 消息订阅队列 A RoutingKey:rk.company
*/
public final static String SUBSCRIBE_QUEUE_NAME_TOPIC_A = "subscribe_queue_topic_A";
/**
* 消息订阅队列 B RoutingKey:rk.company.*
*/
public final static String SUBSCRIBE_QUEUE_NAME_TOPIC_B = "subscribe_queue_topic_B";
/**
* 消息订阅队列 C RoutingKey:*.*.employee
*/
public final static String SUBSCRIBE_QUEUE_NAME_TOPIC_C = "subscribe_queue_topic_C";
/**
* 消息订阅队列 D RoutingKey:#.employee.#
*/
public final static String SUBSCRIBE_QUEUE_NAME_TOPIC_D = "subscribe_queue_topic_D";
/**
* 消息订阅队列 E RoutingKey:#.employee
*/
public final static String SUBSCRIBE_QUEUE_NAME_TOPIC_E = "subscribe_queue_topic_E";
/**
* 消息订阅队列 F RoutingKey:rk.other
*/
public final static String SUBSCRIBE_QUEUE_NAME_TOPIC_F = "subscribe_queue_topic_F";
/**
* 路由RoutingKey A
*/
public final static String ROUTINGKEY_A = "rk.company";
/**
* 路由RoutingKey B
*/
public final static String ROUTINGKEY_B = "rk.company.*";
/**
* 路由RoutingKey C
*/
public final static String ROUTINGKEY_C = "*.*.employee";
/**
* 路由RoutingKey D
*/
public final static String ROUTINGKEY_D = "#.employee.#";
/**
* 路由RoutingKey E
*/
public final static String ROUTINGKEY_E = "#.employee";
/**
* 路由RoutingKey F
*/
public final static String ROUTINGKEY_F = "rk.other";
}
4.1.1 生产者
我们通过设置不同的RoutingKey ,然后观察 队列,看队列中是否根据我们的猜想 接收到了消息
package topic;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import conn.MqConnectUtil;
import subscrib3.ExchangeTypeEnum;
import java.time.LocalDate;
import java.time.LocalTime;
import static topic.TopicConst.*;
public class TopicProducer {
/**
* 生产 Direct直连 交换机的MQ消息
*/
public static void produceTopicExchangeMessage(String id, String rk) throws Exception {
// 获取到连接以及mq通道
Connection connection = MqConnectUtil.getConnectionDefault();
// 从连接中创建通道
Channel channel = connection.createChannel();
/*声明 直连交换机 交换机 String exchange,
* 参数明细
* 1、交换机名称
* 2、交换机类型,fanout
*/
channel.exchangeDeclare(ExchangeTypeEnum.TOPIC.getName(), ExchangeTypeEnum.TOPIC.getType());
/*声明队列
* 参数明细:
* 1、队列名称
* 2、是否持久化
* 3、是否独占此队列
* 4、队列不用是否自动删除
* 5、参数
*/
channel.queueDeclare(SUBSCRIBE_QUEUE_NAME_TOPIC_A, true, false, false, null);
channel.queueDeclare(SUBSCRIBE_QUEUE_NAME_TOPIC_B, true, false, false, null);
channel.queueDeclare(SUBSCRIBE_QUEUE_NAME_TOPIC_C, true, false, false, null);
channel.queueDeclare(SUBSCRIBE_QUEUE_NAME_TOPIC_D, true, false, false, null);
channel.queueDeclare(SUBSCRIBE_QUEUE_NAME_TOPIC_E, true, false, false, null);
channel.queueDeclare(SUBSCRIBE_QUEUE_NAME_TOPIC_F, true, false, false, null);
/*交换机和队列绑定String queue, String exchange, String routingKey
* 参数明细
* 1、队列名称
* 2、交换机名称
* 3、路由key rk.subscribe_queue_direct
*/
channel.queueBind(SUBSCRIBE_QUEUE_NAME_TOPIC_A, ExchangeTypeEnum.TOPIC.getName(), ROUTINGKEY_A);
channel.queueBind(SUBSCRIBE_QUEUE_NAME_TOPIC_B, ExchangeTypeEnum.TOPIC.getName(), ROUTINGKEY_B);
channel.queueBind(SUBSCRIBE_QUEUE_NAME_TOPIC_C, ExchangeTypeEnum.TOPIC.getName(), ROUTINGKEY_C);
channel.queueBind(SUBSCRIBE_QUEUE_NAME_TOPIC_D, ExchangeTypeEnum.TOPIC.getName(), ROUTINGKEY_D);
channel.queueBind(SUBSCRIBE_QUEUE_NAME_TOPIC_E, ExchangeTypeEnum.TOPIC.getName(), ROUTINGKEY_E);
channel.queueBind(SUBSCRIBE_QUEUE_NAME_TOPIC_F, ExchangeTypeEnum.TOPIC.getName(), ROUTINGKEY_F);
//定义消息内容(发布多条消息)
String message = "id=" + id + " Hello World! Time:" + LocalDate.now() + " " + LocalTime.now();
/* 发送消息 String exchange, String routingKey, BasicProperties props, byte[] body
* exchange - 交换机 DirectExchange
* queuename - 队列信息
* props - 参数信息
* message 消息体 byte[]类型
*/
channel.basicPublish(ExchangeTypeEnum.TOPIC.getName(), rk, null, message.getBytes());
System.out.println(" **** Producer Sent Message: '" + message + "'");
//关闭通道和连接
channel.close();
connection.close();
}
/**
* 生产 1 消息,rk.company
*/
public static void produce1() throws Exception {
//只有A队列有,B队列 rk.company.*没有,因为 B队列以 .* 结尾,要匹配一个单词,不能匹配 rk.company
produceTopicExchangeMessage("A", ROUTINGKEY_A);
}
/**
* 生产 2 消息,rk.company.aaaa
*/
public static void produce2() throws Exception {
//只有B队列有,B队列 rk.company.* 可以匹配任意一个单词,可以匹配 rk.company.aaaa
produceTopicExchangeMessage("A", "rk.company.aaaa");
}
/**
* 生产 3 消息,rk.employee
*/
public static void produce3() throws Exception {
//只有 D、E 队列可以收到
produceTopicExchangeMessage("A", "rk.employee");
}
/**
* 生产 4 消息,rk.employee
*/
public static void produce4() throws Exception {
//只有D队列
produceTopicExchangeMessage("A", "rk.employee.ccc");
}
/**
* 生产 5 消息,rk.test.employee
*/
public static void produce5() throws Exception {
//只有C、D、E 队列
produceTopicExchangeMessage("A", "rk.test.employee");
}
public static void main(String[] argv) throws Exception {
// produce1();
// produce2();
// produce3();
// produce4();
// produce5();
}
}
4.1.2 produce1 运行
produce1 以 “rk.company” 发消息,运行
先看下队列 6个队列,分别绑定到 exchange topic的主题交换机上
而且现在6个队列中是没有消息的,积攒的消息全都是 0
4.1.3 produce1 结果
以"rk.company" 发消息,结果只有队列A RoutingKey: rk.company 精确匹配,收到了1条消息
分析:
rk.company的RoutingKey,不能匹配队列B,因为队列B以.* 结尾,会寻找 不为零的一个单词 来匹配队列,所以只有A可以匹配
/**
* 生产 1 消息,rk.company
*/
public static void produce1() throws Exception {
//只有A队列有,B队列 rk.company.*没有,因为 B队列以 .* 结尾,要匹配一个单词,不能匹配 rk.company
produceTopicExchangeMessage("A", ROUTINGKEY_A);
}
队列结果
下面我们在界面上操作,清空队列A中的消息,避免影响到下一个测试
Purge Messages 清空有消息的队列,继续下一步
4.1.3 produce2 运行-结果
以"rk.company.aaaa" 发消息,结果只有队列A RoutingKey: rk.company 精确匹配,收到了1条消息
分析:
rk.company.aaa 的RoutingKey,只能匹配队列B,因为队列B以.* 结尾,会寻找 不为零的一个单词 来匹配队列,所以只有B可以匹配,对立A是标准严格 rk.company匹配
/**
* 生产 2 消息,rk.company.aaaa
*/
public static void produce2() throws Exception {
//只有B队列有,B队列 rk.company.* 可以匹配任意一个单词,可以匹配 rk.company.aaaa
produceTopicExchangeMessage("A", "rk.company.aaaa");
}
查看队列结果
Purge Messages 清空有消息的队列,继续下一步
4.1.4 produce3 运行-结果
以 rk.employee 发消息,结果只有 D、E 队列可以收到
分析:C队列RoutingKey :*.*.employee 要匹配 xx.xx.employee两个单词,不匹配
D队列RoutingKey : #.employee.# 可以匹配 0个单词的x.employee.x ,所以可以收到
E队列RoutingKey : #.employee 可以匹配 0个单词的 x.employee结尾的 ,所以可以收到
F队列RoutingKye : rk.other 只匹配固定字符的 rk.other
/**
* 生产 3 消息,rk.employee
*/
public static void produce3() throws Exception {
//只有 D、E 队列可以收到
produceTopicExchangeMessage("A", "rk.employee");
}
查看队列结果,没问题,只有D、E收到消息
Purge Messages 清空有消息的队列,继续下一步
4.1.5 produce4 运行-结果
以 rk.employee.ccc 发消息,结果只有 D 队列可以收到
分析:C队列RoutingKey :*.*.employee 要匹配 xx.xx.employee两个单词,不匹配
D队列RoutingKey : #.employee.# 可以匹配 0个单词的x.employee.x ,所以可以收到
E队列RoutingKey : #.employee 可以匹配 0个单词的 x.employee结尾的 ,结尾不是employee 不匹配
F队列RoutingKye : rk.other 只匹配固定字符的 rk.other
/**
* 生产 4 消息,rk.employee
*/
public static void produce4() throws Exception {
//只有D队列
produceTopicExchangeMessage("A", "rk.employee.ccc");
}
查看队列结果,没问题,只有D 收到消息
Purge Messages 清空有消息的队列,继续下一步
4.1.6 produce5 运行-结果
以 rk.test.employee 发消息,结果只有 D 队列可以收到
分析:C队列RoutingKey :*.*.employee 要匹配 xx.xx.employee两个单词,刚好可以匹配
D队列RoutingKey : #.employee.# 可以匹配 0个单词的x.employee.x ,所以可以收到
E队列RoutingKey : #.employee 可以匹配 0个单词或者多个单词的 x.employee结尾的 ,也可以匹配
F队列RoutingKye : rk.other 只匹配固定字符的 rk.other
/**
* 生产 5 消息,rk.test.employee
*/
public static void produce5() throws Exception {
//只有C、D、E 队列
produceTopicExchangeMessage("A", "rk.test.employee");
}
查看队列结果,没问题,只有C、D、E 可以收到消息
Purge Messages 清空有消息的队列,继续下一步
下篇我们介绍 RabbitMQ系列(八)RabbitMQ进阶-Queue队列特性 (三) 发布/订阅 模式(4)-头交换机