RabbitMQ进阶实战:三种典型消息路由模式详解(订阅/路由/主题)

读前须知

本篇为下述文章的续作从零开始学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 ,后续交换机会根据 RoutingKeyBindingKey的匹配规则把消息路由到对应的交换机中

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订阅的队列,获取一下消息并观察:

启动消费者:

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

相关推荐
huohaiyu2 小时前
UDP协议
网络·网络协议·udp
一轮弯弯的明月2 小时前
TCP连接管理(三次握手与四次挥手)
网络·经验分享·笔记·网络协议·tcp/ip·学习心得
无心水2 小时前
4、Go语言程序实体详解:变量声明与常量应用【初学者指南】
java·服务器·开发语言·人工智能·python·golang·go
sheji34162 小时前
【开题答辩全过程】以 食堂兼职管理系统为例,包含答辩的问题和答案
java·eclipse
0x532 小时前
JAVA|智能仿真并发项目-并行与并发
java·开发语言
init_23612 小时前
【HCIE-08】NAT64
linux·服务器·网络
沛沛老爹2 小时前
从Web到AI:多模态Agent图像识别Skills开发实战——JavaScript+Python全栈图像处理方案
java·javascript·图像处理·人工智能·python·rag
朴实赋能2 小时前
人工智能大模型+智能体:建筑行业数字化转型的“三级金字塔“实践路径
java·后端·struts
enjoy编程2 小时前
Spring-AI Agent Skills 赋予AI智能体“即插即用”的专业超能力 --II
java·人工智能·spring