读前须知
本篇为下述文章的续作从零开始学RabbitMQ:Java实战简单模式与工作队列消息处理
https://blog.csdn.net/2302_81247414/article/details/152127950?fromshare=blogdetail&sharetype=blogdetail&sharerId=152127950&sharerefer=PC&sharesource=2302_81247414&sharefrom=from_link 在上篇中我们讲解了简单队列模式和工作队列模式,现我们来回顾一下使用消息队列前,需要做哪些步骤:
(1)我们要先创建一个连接ConnectionFactory对象,用来管理和创建连接
(2)向ConnectionFactory对象设置主机,端口号,账号,密码和虚拟主机等属性
(3)创建连接 connect 和 通道 channel
(4)声明交换机和队列
(5)构造消息发送或从队列中获取信息
一、订阅模式

订阅模式是一对多的广播模式,类似于气象台每日把天气信息发送给每个订阅者,由上图可知,该模式由一个交换机,多个队列,多个消费者组成,每个消费者都能接收到来自交换机发出的消息。
1.1、生产者
java
public class FanoutMQProduce {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(Constants.HOST);
connectionFactory.setPort(Constants.PORT);
connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);
connectionFactory.setUsername(Constants.USER_NAME);
connectionFactory.setPassword(Constants.PASSWORD);
Connection connection=connectionFactory.newConnection();
Channel channel=connection.createChannel();
//声明交换机
channel.exchangeDeclare(Constants.FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT,true);
//声明队列
channel.queueDeclare(Constants.FANOUT_QUEUE1,true,false,false,null);
channel.queueDeclare(Constants.FANOUT_QUEUE2,true,false,false,null);
//交换机和队列绑定,routingKey为空表示交换机收到任何消息都会直接发送到队列中
channel.queueBind(Constants.FANOUT_QUEUE1,Constants.FANOUT_EXCHAGE,"");
channel.queueBind(Constants.FANOUT_QUEUE2,Constants.FANOUT_EXCHAGE,"");
//发布消息
String str="hello fanoutMQ~~~~~";
channel.basicPublish(Constants.FANOUT_EXCHAGE,"",null,str.getBytes());
//释放资源
channel.close();
connection.close();
}
}
协议规范 :RabbitMQ协议规定,Fanout交换机的绑定操作中,routingKey 参数必须为空字符串
方法签名
basicPublish(String exchange, String routingKey, AMQP.BasicProperties props, byte[] body)
参数说明
- Exchange:指定消息发送的交换机名称
- RoutingKey :当交换机类型为 Fanout 时,需设置为空字符串 :"",表示所有消费者都能接收到该消息
在图形化界面上,可以看到该交换机已经绑定下列两个队列

1.2、消费者
(两个消费者,分别声明不同的队列)
java
public class FanoutMQConsumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.建立连接
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(Constants.HOST);
connectionFactory.setPort(Constants.PORT);
connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);
connectionFactory.setUsername(Constants.USER_NAME);
connectionFactory.setPassword(Constants.PASSWORD);
Connection connection=connectionFactory.newConnection();
//2. 创建channel通道
Channel channel = connection.createChannel();
//3.声明队列
channel.queueDeclare(Constants.FANOUT_QUEUE1,true,false,false,null);
//4.消费消息
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) {
System.out.println(new String(body));
}
};
channel.basicConsume(Constants.FANOUT_QUEUE1,true,consumer);
}
}
分别运行两个消费者,下图可发现,两个消费者都能接受到生产者发送的消息


二、路由模式

2.1、生产者
生产者首先进行一些必要的操作(见前言),然后声明交换机和队列(此处交换机的类型我们选择Direct),接着根据 Routingkey 路由绑定对应的交换机和队列,最后再按照不同的路由规则发布消息。
java
public class RoutingMQProduce {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(Constants.HOST);
connectionFactory.setPort(Constants.PORT);
connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);
connectionFactory.setUsername(Constants.USER_NAME);
connectionFactory.setPassword(Constants.PASSWORD);
Connection connection=connectionFactory.newConnection();
Channel channel=connection.createChannel();
// 声明交换机和队列
channel.exchangeDeclare(Constants.Routing_EXCHAGE, BuiltinExchangeType.DIRECT);
channel.queueDeclare(Constants.Routing_QUEUE2,false,false,false,null);
channel.queueDeclare(Constants.Routing_QUEUE1,false,false,false,null);
// 绑定交换机和队列
channel.queueBind(Constants.Routing_QUEUE1,Constants.Routing_EXCHAGE,"error");
channel.queueBind(Constants.Routing_QUEUE2,Constants.Routing_EXCHAGE,"info");
channel.queueBind(Constants.Routing_QUEUE2,Constants.Routing_EXCHAGE,"error");
channel.queueBind(Constants.Routing_QUEUE2,Constants.Routing_EXCHAGE,"warning");
// 构造消息
String str1="今天的天气是 ☀️ (error)️";
String str2="今天的天气是 ☔️ (info)️";
String str3="今天的天气是 ❄️ (warning)️";
// 根据不同的路由规则,发布消息
channel.basicPublish(Constants.Routing_EXCHAGE,"error",null,str1.getBytes());
channel.basicPublish(Constants.Routing_EXCHAGE,"info",null,str2.getBytes());
channel.basicPublish(Constants.Routing_EXCHAGE,"warning",null,str3.getBytes());
channel.close();
connection.close();
}
}
2.2、消费者
(此处有两个消费者分别订阅不同的队列,见路由模式图,代码逻辑类似,只是消费消息中basicConsume 方法里的队列不同,故此处仅列举一个消费者)
java
public class RoutingMQConsumer1 {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(Constants.HOST);
connectionFactory.setPort(Constants.PORT);
connectionFactory.setVirtualHost(Constants.VIRTUAL_HOST);
connectionFactory.setUsername(Constants.USER_NAME);
connectionFactory.setPassword(Constants.PASSWORD);
Connection connection=connectionFactory.newConnection();
Channel channel=connection.createChannel();
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
}
};
channel.basicConsume(Constants.Routing_QUEUE1,true,consumer);
Thread.sleep(1200);
channel.close();
connection.close();
}
}
启动生产者,消费者,可见图形化界面显示1条消息发送到消费者1订阅的队列,3条消息发送到消费者2订阅的消息



注意:在消费者代码逻辑中没有看到声明交换机和队列,因为当生产者或消费者其中一方声明交换机或队列后,另一方使用时可以不再声明(对于已经存在的交换机和队列,可以直接使用),但是建议双方都声明一下,倘若不存在系统会报错
三、主题模式

在讲解主题模式之前。我们要先了解Routingkey和BindingKey
3.1、Routingkey 和 BindingKey
3.1.1、RoutingKey
RoutingKey 是消息队列中用于将消息路由到特定队列的键,生产者发送特定消息时指定RoutingKey,交换机根据 RoutingKey 和 绑定规则 决定将消息投递到哪些队列.
交换机为Fanout类型时,Routingkey为空字符串,表示发往所有绑定该交换机的队列
其他类型:字符串,以 . 来分割每一部分,例如:aa.orange.bb
3.1.2、BindingKey
BindingKey 是消费者将队列绑定到交换机时指定的匹配规则键,它决定了消息如何从交换机路由到队列
3.1.3、匹配规则
| Exchange类型 | BindingKey格式 | RoutingKey格式 | 匹配规则 | 示例 |
|---|---|---|---|---|
| Direct | 字符串 | 字符串 | 完全相等 | BK="aaa.bbb", RK="aaa.bbb" 成功 |
| Topic | 含通配符的字符串 | 点分隔字符串 | 模式匹配 | BK="aaa.*", RK="aaa.bbb" 成功 |
| Fanout | 任意(忽略) | 任意(忽略) | 不匹配,全部接收 | 任何BK/RK组合 成功 |
⚠️:在Topic类型的交换机中,BindingKey可以包含两个模糊匹配符' * ' 和 ' # '
*:严格匹配一个单词
#:灵活匹配零个、一个或多个单词
3.2、生产者
我们仅展现核心代码,前面的内容和其他模式的一样。声明交换机时类型选择TOPIC 类型,绑定交换机和队列时,BindingKey 中使用模糊匹配符,发送消息时指定 RoutingKey ,后续交换机会根据 RoutingKey 和 BindingKey的匹配规则把消息路由到对应的交换机中
java
//声明交换机和队列
channel.exchangeDeclare(Constants.Topic_EXCHAGE,BuiltinExchangeType.TOPIC,true,false,null);
channel.queueDeclare(Constants.Topic_QUEUE1,true,false,false,null);
channel.queueDeclare(Constants.Topic_QUEUE2,true,false,false,null);
//绑定交换机和队列
channel.queueBind(Constants.Topic_QUEUE1,Constants.Topic_EXCHAGE,"*.orange.*");
channel.queueBind(Constants.Topic_QUEUE2,Constants.Topic_EXCHAGE,"*.*.rabbit");
channel.queueBind(Constants.Topic_QUEUE2,Constants.Topic_EXCHAGE,"lazy.#");
//构造消息
String str1="我的主题是 aa.orange.bb "; //发往1
String str2="我的主题是 aa.bb.rabbit "; //发往2
String str3="我的主题是 lazy.orange.aa"; //发往1和2
//发送消息
channel.basicPublish(Constants.Topic_EXCHAGE,"aa.orange.bb",null,str1.getBytes());
channel.basicPublish(Constants.Topic_EXCHAGE,"aa.bb.rabbit",null,str2.getBytes());
channel.basicPublish(Constants.Topic_EXCHAGE,"lazy.orange.aa",null,str3.getBytes());

3.3、消费者
java
DefaultConsumer consumer = 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));
}
};
channel.basicConsume(Constants.Topic_QUEUE2,true,consumer);
Thread.sleep(1000);
channel.close();
connection.close();
启动生产者后:

打开消费者2订阅的队列,获取一下消息并观察:

启动消费者:

本文深入探讨了订阅、路由和主题模式的应用场景。虽然不同交换机类型的操作具有诸多相似之处,但正是这些细微差异构成了它们的核心区别。
