RabbitMQ->Topic
在看了 上一个实验 RabbitMQ->路由订阅模型(direct) 之后,发现分级消息通知很适合我们的生活中的实际场景。
实验代码:CODE CHINA
目录
Step2 创建 工具类 ConnectionUtil.java
反思
反思上一个实验:
但是我们仔细思考一下,一个班级有很多个科任老师,每个科任老师都有自己的课程内容。
如果按照上面一个实验的流程来写,
// EXCHANGE_NAME (班主任) // 绑定 info 键值 channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_INFO); // 绑定 warning 键值 channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_WARNING); // 绑定 error 键值 channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_ERROR); // EXCHANGE_NAME (数学老师) // 绑定 info 键值 channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_INFO); // 绑定 warning 键值 channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_WARNING); // 绑定 error 键值 channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_ERROR); // EXCHANGE_NAME (英语老师) // 绑定 info 键值 channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_INFO); // 绑定 warning 键值 channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_WARNING); // 绑定 error 键值 channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_ERROR); // ...
可以发现,代码会随着老师(交换机)的增多,代码的冗余度有点惨不忍睹
当然,我们能考虑到的,RabbitMQ的工作人员早就想到了,因此推出了 TOPIC 模型,
在 TOPIC 模型中,可以使用通配符进行绑定: * 匹配全部,当然前提是交换机的类型 为 topic 类型
在 topic 模式中有两个通配符:
* (start) can substitute for exactly one word. 匹配一个单词
# (hash) can substitute for zero or more words. 匹配一个或多个单词
在 topic 模式中,RoutingKey 一般是由多个单词组成的
以上图为例,
*.dcpnet.* 能匹配到三个队列
cn.# 能匹配到第一个和第三个队列
实验
设计
我们设定9个角色:三个消息生产者(校长、班主任、语文老师)、7个+消息消费者(一班班长、一班语文课代表、一班全体学生,二班语文课代表、二班学生、体育老师、其他老师)
分析:
班主任只带一个班:管理一班学生,有一个班长。通知一班学生放学,班长整队
语文老师带两个班的语文:管理两个班的语文,和两个语文课代表。通知两个班学生语文作业,通知语文课代表明天作业收到办公室
校长:管理全校学生和体育老师,通知全校学生进行课间操,体育老师组织
这个实验只是简单模拟学校的部分事务。
语文老师布置作业:student.# 发送消息-> 作业内容
通知课代表收作业: manager.*.chinese. 发送消息-> 要收的作业
班主任通知放学 : student.1.* 发送消息 -> 放学了,到门口排队
通知班长:manager.1.monitor 发送消息 -> 整队带出去
校长通知课间操:# 全校师生课间操
体育老师: teacher.sports.* 组织课间操
开启项目
代码
添加依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.3</version>
</dependency>
Step 1 创建常量类 Constant.java
其中包含三个交换机的名字、路由键值、消息内容
// 交换机名字:语、数、外老师
/** 语文老师 */
public static final String EXCHANGE_CHINESE_TEACHER = "ex_chinese";
/** 班主任 */
public static final String EXCHANGE_HEAD_TEACHER = "ex_head";
/** 校长 */
public static final String EXCHANGE_HEAD_MASTER= "ex_headmaster";
// 消息内容
/** 语文老师给所有同学的消息 : student.*.all */
public static final String MESSAGE_TO_STUDENT_ALL = ">语文老师: 语文作业内容";
/** 语文老师给语文课代表的消息 : student.*.chinese*/
public static final String MESSAGE_TO_STUDENT_CHINESE = ">语文老师: 明天把语文作业收了,抱我办公室来";
/** 班主任(1)给所有学生的消息 : student.1.* */
public static final String MESSAGE_TO_STUDENT_ONE = ">班主任1: 放学了,到门口排队";
/** 班主任给班长(1)的消息 : student.1.monitor */
public static final String MESSAGE_TO_MONITOR = ">班主任1: 组织放学,整队";
/** 校长给全校的消息 : #.all */
public static final String MESSAGE_TO_ALL = ">校长: 所有人到操场来课间操";
/** 校长给体育老师的消息 : teacher.sports.* */
public static final String MESSAGE_TO_TEACHER_SPORTS_ALL = ">校长: 体育老师带领课间操";
Step2 创建 工具类 ConnectionUtil.java
ConnectionUtil 中主要功能是 获取连接、释放资源
public class ConnectionUtil {
/** Ip 地址 */
private static final String IP_ADDRESS = "127.0.0.1";
/** 固定端口 */
private static final int PORT = 5672;
// TODO 改成你自己的用户名
/** 用户名 */
private static final String USER_NAME = "root";
// TODO 改成你自己的密码
/** 密码 */
private static final String PASSWORD = "dcpnet";
/**
* 获取连接
* @return 连接
* @throws IOException IOException
* @throws TimeoutException TimeOutException
*/
public static Connection getConnection() throws IOException, TimeoutException {
/** 创建连接工厂 */
ConnectionFactory connectionFactory = new ConnectionFactory();
// 设置连接工厂属性
// IP 地址
connectionFactory.setHost(IP_ADDRESS);
// 端口
connectionFactory.setPort(PORT);
// 用户名
connectionFactory.setUsername(USER_NAME);
// 密码
connectionFactory.setPassword(PASSWORD);
// 创建新的连接并且返回
return connectionFactory.newConnection();
}
/**
* 关闭资源
* @param connection 连接
* @param channel 信道
*/
public static void close(Connection connection, Channel channel){
try {
channel.close();
connection.close();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
Step3 创建 生产者 Producer
public class ProducerClient {
public static void main(String[] args) throws Exception{
// 获取连接、信道
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// TODO 创建交换机:校长、班主任、语文老师
/* channel.exchangeDeclare(Constant.EXCHANGE_HEAD_MASTER,"topic",true);
channel.exchangeDeclare(Constant.EXCHANGE_HEAD_TEACHER,"topic",true);
channel.exchangeDeclare(Constant.EXCHANGE_CHINESE_TEACHER,"topic",true);*/
// 校长通知
// 所有人做课间操
channel.basicPublish(Constant.EXCHANGE_HEAD_MASTER,"dcpnet.all.all",null,Constant.MESSAGE_TO_ALL.getBytes());
// 体育老师带操
channel.basicPublish(Constant.EXCHANGE_HEAD_MASTER,"dcpnet.teacher.sports.all",null,Constant.MESSAGE_TO_TEACHER_SPORTS_ALL.getBytes());
// 语文老师通知
channel.basicPublish(Constant.EXCHANGE_CHINESE_TEACHER,"dcpnet.student.all.all",null,Constant.MESSAGE_TO_STUDENT_ALL.getBytes());
channel.basicPublish(Constant.EXCHANGE_CHINESE_TEACHER,"dcpnet.manager.all.chinese",null,Constant.MESSAGE_TO_STUDENT_CHINESE.getBytes());
// 班主任
channel.basicPublish(Constant.EXCHANGE_HEAD_TEACHER,"dcpnet.student.1.all",null,Constant.MESSAGE_TO_STUDENT_ONE.getBytes());
channel.basicPublish(Constant.EXCHANGE_HEAD_TEACHER,"dcpnet.manager.1.monitor",null,Constant.MESSAGE_TO_MONITOR.getBytes());
// 释放资源
ConnectionUtil.close(connection,channel);
}
}
Step4 创建 消费者 Consumer
StudentC1N1
接受校长给所有人的全部消息
接受一班班主任给所有人的消息
接受一般班主任给班长的消息
接受语文老师给所有人的消息
public class StudentC1N1 {
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 获取一个临时队列
String queueName = channel.queueDeclare().getQueue();
// 绑定到 Exchange 班主任、语文老师、校长 的 student.1.1
channel.queueBind(queueName,Constant.EXCHANGE_HEAD_TEACHER,"dcpnet.student.1.*");
channel.queueBind(queueName,Constant.EXCHANGE_CHINESE_TEACHER,"dcpnet.student.#");
channel.queueBind(queueName,Constant.EXCHANGE_HEAD_MASTER,"dcpnet.all.#");
// 绑定到 Exchange 班主任 班长
channel.queueBind(queueName,Constant.EXCHANGE_HEAD_TEACHER,"dcpnet.manager.1.monitor");
channel.basicConsume(queueName,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("[一班学生1-班长] 接收到消息 : " + new String(body));
}
});
}
}
StudentC1N2
接受校长给所有人的全部消息
接受一班班主任给所有人的消息
接受语文老师给所有人的消息
接受语文老师给语文课代表的消息
public class StudentC1N2 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 获取一个临时队列
String queueName = channel.queueDeclare().getQueue();
// 绑定到 Exchange 班主任、语文老师、校长 的 student.1.2
channel.queueBind(queueName, Constant.EXCHANGE_HEAD_TEACHER, "dcpnet.student.1.*");
channel.queueBind(queueName, Constant.EXCHANGE_CHINESE_TEACHER, "dcpnet.student.#");
channel.queueBind(queueName, Constant.EXCHANGE_HEAD_MASTER, "dcpnet.all.#");
// 绑定到 Exchange 语文老师 的 语文课代表
channel.queueBind(queueName, Constant.EXCHANGE_CHINESE_TEACHER,"dcpnet.manager.*.chinese");
channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("[一班学生2-语文课代表] 接收到消息 : " + new String(body));
}
});
}
}
StudentC1Nn
接受校长给所有人的全部消息
接受一班班主任给所有人的消息
接受语文老师给所有人的消息
public class StudentC1Nn {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 获取一个临时队列
String queueName = channel.queueDeclare().getQueue();
// 绑定到 Exchange 班主任、语文老师、校长 的 student.1.2
channel.queueBind(queueName, Constant.EXCHANGE_HEAD_TEACHER, "dcpnet.student.1.*");
channel.queueBind(queueName, Constant.EXCHANGE_CHINESE_TEACHER, "dcpnet.student.#");
channel.queueBind(queueName, Constant.EXCHANGE_HEAD_MASTER, "dcpnet.all.#");
channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("[一班学生n] 接收到消息 : " + new String(body));
}
});
}
}
StudentC2N1
接受校长给所有人的全部消息
接受语文老师给所有人的消息
接受语文老师给课代表的消息
public class StudentC2N1 {
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 获取一个临时队列
String queueName = channel.queueDeclare().getQueue();
// 绑定到 Exchange 班主任、语文老师、校长 的 student.2.1
channel.queueBind(queueName,Constant.EXCHANGE_CHINESE_TEACHER,"dcpnet.student.#");
channel.queueBind(queueName,Constant.EXCHANGE_HEAD_MASTER,"dcpnet.all.#");
// 绑定到 Exchange 班主任 班长
channel.queueBind(queueName,Constant.EXCHANGE_CHINESE_TEACHER,"dcpnet.manager.*.chinese");
channel.basicConsume(queueName,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("[二班学生1-语文课代表] 接收到消息 : " + new String(body));
}
});
}
}
StudentC2Nn
接受校长给所有人的全部消息
接受语文老师给所有人的消息
public class StudentC2Nn {
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 获取一个临时队列
String queueName = channel.queueDeclare().getQueue();
// 绑定到 Exchange 语文老师 的 student.2.n
channel.queueBind(queueName,Constant.EXCHANGE_CHINESE_TEACHER,"dcpnet.student.#");
channel.queueBind(queueName,Constant.EXCHANGE_HEAD_MASTER,"dcpnet.all.#");
channel.basicConsume(queueName,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("[二班学生n] 接收到消息 : " + new String(body));
}
});
}
}
TeacherSportN1
接受校长给所有人的全部消息
接受校长给体育老师的消息
public class TeacherSportsN1 {
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 获取一个临时队列
String queueName = channel.queueDeclare().getQueue();
// 所有教师的消息
channel.queueBind(queueName,Constant.EXCHANGE_HEAD_MASTER,"dcpnet.all.#");
// 体育教师的消息
channel.queueBind(queueName,Constant.EXCHANGE_HEAD_MASTER,"dcpnet.teacher.sports.*");
channel.basicConsume(queueName,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("[体育老师1] 接收到消息 : " + new String(body));
}
});
}
}
TeacherOtherNn
接受校长给所有人的全部消息
public class TeacherOtherNn {
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 获取一个临时队列
String queueName = channel.queueDeclare().getQueue();
// 所有教师
channel.queueBind(queueName,Constant.EXCHANGE_HEAD_MASTER,"dcpnet.all.#");
channel.basicConsume(queueName,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("[Other老师n] 接收到消息 : " + new String(body));
}
});
}
}
Step 5 运行程序
首先确保有三个交换机
// 交换机名字:语、数、外老师
/** 语文老师 */
public static final String EXCHANGE_CHINESE_TEACHER = "ex_chinese";
/** 班主任 */
public static final String EXCHANGE_HEAD_TEACHER = "ex_head";
/** 校长 */
public static final String EXCHANGE_HEAD_MASTER= "ex_headmaster";
如果没有这三个 topic 类型的交换机,则先进行创建
方式1:
将生产者中 TODO 注释后面的几行代码取消注释,第一次执行的时候运行
方式二:
在web 管理页面添加
正式开始运行:
1,首先运行所有的消费者
2.运行生产者发送消息
可以看到,
学生1-班长 接收到了校长给所有人的消息、班主任给所有人的消息、语文老师给所有人的消息、班主任给班长的消息
学生2-语文课代表 接收到了校长给所有人的消息、班主任给所有人的消息、语文老师给所有人的消息、语文老师给语文课代表的消息
学生n 接收到了校长给所有人的消息、班主任给所有人的消息、语文老师给所有人的消息
学生2-语文课代表 接收到了校长给所有人的消息、语文老师给所有人的消息、语文老师给语文课代表的消息
学生2-n 接收到了校长给所有人的消息、语文老师给所有人的消息
体育教师1 接收到了校长给所有热的消息、校长给体育老师的消息
其他老师n 接收到了校长给所有的老师的消息