中间件系列四 RabbitMQ之Fanout exchange(扇型交换机)之发布订阅

本文介绍如何使用RabbitMQ的Fanout Exchange(扇形交换机)实现消息广播,使得一条消息能被多个消费者接收到。文章详细介绍了生产者和消费者的实现过程,并提供了测试代码。

1. 概述

本文通过Fanout exchange(扇型交换机)实现生产者发送一个消息,这个消息同时被传送给所有消费者。Fanout exchange(扇型交换机)的定义见这篇文章 中间件系列三 RabbitMQ之交换机的四种类型和属性

本文主要内容:

  • 实现上文定义的场景
  • 交换机方法的声明方法、参数说明和实现
  • 临时队列的作用、声明方法和实现
  • 绑定(Bindings)的方法、参数说明和实现
  • 测试

下文先分别介绍生产者和消费者的代码,然后对其进行测试,模拟上面的发布/订阅场景。完整的代码在文末

2. 生产者代码

主要逻辑在这个类中: Publish.java

1. 配置连接工厂
2. 建立TCP连接
3. 在TCP连接的基础上创建通道
4. 声明一个fanout交换机 (而不是声明一个队列)
5. 发送消息

这里和之前文章最大的不同是,多了声明一个fanout交换机,而不是声明队列。这里表明我们不使用默认交换机,在消费端需要执行队列和交换机的绑定操作

声明交换机方法

Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete,
                                       Map<String, Object> arguments) throws IOException

详细参数如下:

第一个参数exchange:交换机的名称
第二个参数type:交换机的类型
第三个参数durable:是否持久化,如果true,则当前RabbitMQ重启的时候,它依旧存在
第四个参数autoDelete:当没有生成者/消费者使用此交换机时,此交换机会被自动删除。
第五个参数arguments:其它的扩展属性

代码如下

// 声明一个fanout交换机
channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.FANOUT, false, false, null);
// 发送消息
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));

完整代码
发送者代码: Publish.java

3. 消费者代码

消费者主要逻辑在这个类中: Publish.java

1. 配置连接工厂
2. 建立TCP连接
3. 在TCP连接的基础上创建通道
4. 声明一个fanout交换机 
5. 声明一个临时队列
6. 将临时队列绑定到交换机上
7. 接收消息并处理

这里和之前文章最大的不同是,多了声明一个fanout交换机,而不是声明队列。这里表明我们不使用默认交换机,在消费端需要执行队列和交换机的绑定操作

临时队列:
在本例子中,我们使用临时队列。临时队列有以下特点:
1. 消费者每次连接时,都会创建一个新的队列,队列名称随机生成
2. 当消费者断开连接时,队列会自动变删除

在RabbitMQ中,随机使用的队列名称类似amq.gen-**

声明方法:

Queue.DeclareOk queueDeclare() throws IOException;  

此方法等价于创建一个
Queue.DeclareOk queueDeclare(String queue="自动生成类似amq.gen-******的随机名称", boolean durable=false, boolean exclusive=true, boolean autoDelete=true,  Map<String, Object> arguments=null) throws IOException;

代码:创建临时队列,得到队列名称,用于后面的绑定使用

String queueName = channel.queueDeclare().getQueue();

绑定(Bindings)
这里写图片描述

在创建交换机、队列后,要实现将发送到特定的交换机X的消息路由到我们创建的队列,还需要做绑定操作。如果绑定成功后,交换机会将带特定路由键的消息路由到绑定的队列

方法:将队列绑定到交换机上

Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOException;

详细参数如下:

第一个参数queue:要绑定的队列
第二个参数exchange:要绑定的交换机
第三个参数routingKey:绑定使用的路由键

代码:将队列绑定到交换机上,因为我们这个demo是使用扇形交换机,routingKey是没有意义的,所以这里使用的routingKey为空字符串

String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "");

完整代码
消费者代码:Subscribe.java

4. 测试

测试代码

public class BasicTest {
    // 测试线程池
    private ExecutorService executorService = Executors.newFixedThreadPool(10);

    // rabbitmq的IP地址
    private final String rabbitmq_host = "10.240.80.147";
    // rabbitmq的用户名称
    private final String rabbitmq_user = "hry";
    // rabbitmq的用户密码
    private final String rabbitmq_pwd = "hry";

    @Test
    public void publishsubscribe() throws InterruptedException {
        // 接收端
        int recNum = 2;
        while(recNum-- > 0) {
            final int recId = recNum;
            executorService.submit(() -> {
                Subscribe.execute(rabbitmq_host, rabbitmq_user, rabbitmq_pwd, recId);
            });
        }
        Thread.sleep(5* 100);
        // 发送端
        int sendNum = 2;
        while(sendNum-- > 0){
            executorService.submit(() -> {
                Publish.execute(rabbitmq_host, rabbitmq_user, rabbitmq_pwd);
            });
        }

        // sleep 10s
        Thread.sleep(10 * 1000);
    }

执行测试,启动两个消费者,发送者发送2条记录,这些记录同时被2个消费者消费

 [Subscribe-0] Waiting for messages.
 [Subscribe-1] Waiting for messages.
 // 发送两条记录
 [Publish] Sent 'Publish-366028801770803'
 [Publish] Sent 'Publish-366028803809517'
 // 消费者1收到两条记录
 [Subscribe-0] Received 'Publish-366028801770803'
 [Subscribe-0] Received 'Publish-366028803809517'
 // 消费者2收到两条记录
 [Subscribe-1] Received 'Publish-366028801770803'
 [Subscribe-1] Received 'Publish-366028803809517'

5.代码

上文的详细代码主要如下:
发送者代码: Publish.java
消费者代码:Subscribe.java
测试代码:BasicTest.java的方法 publishsubscribe()
所有的详细代码见github代码,请尽量使用tag v0.8,不要使用master,因为master一直在变,不能保证文章中代码和github上的代码一直相同

<think>嗯,用户想了解RabbitMQ交换机类型。根据引用内容,最新版本RabbitMQ确实有交换机:Direct、Fanout、Topic和Headers。用户可能正在学习消息队列中间件,或者是开发中需要用到RabbitMQ进行系统设计。 从引用[1]和[2]看,交换机的基础定义很清晰。引用[4]补充了Fanout交换机的广播特性,引用[5]则详细说明了Topic交换机的路由键匹配规则。这些正好能组成完整的回答框架。 用户没有指定具体场景,但主动要求"介绍",说明ta可能处于初学阶段。需要避免过于深入的技术细节,重点讲清楚每种交换机的核心机制和典型应用场景。比如: - Direct适合精确路由(如订单状态更新) - Fanout适合广播(如系统通知) - Topic适合灵活的路由(如日志分级处理) - Headers适合非路由键的复杂匹配(虽然较少用) 考虑在回答中强调实际开发中的选择建议,比如"90%场景用前三种就够了"。这样能帮助用户快速建立认知框架。最后准备几个延伸问题,方便用户按需深入:比如性能对比、SpringBoot集成示例等——引用[3]正好提供了配置片段可以参考。 注意到引用中出现了"五种交换机"的说法(含System exchange),但主流资料都以种为准。决定按种分类回答,避免混淆初学者。</think>好的,RabbitMQ 的核心路由机制依赖于交换机Exchange),生产者将消息发送到交换机交换机根据其类型和绑定规则将消息路由到一个或多个队列。最新版本 RabbitMQ 主要支持以下交换机类型: 1. **直连交换机 (Direct Exchange)** * **工作原理:** 这是最简单、最常用的交换机类型。它将消息路由到那些 Binding Key(绑定键)与消息的 Routing Key(路由键)**完全匹配** 的队列。 * **路由键匹配规则:** 精确匹配 (`routing_key == binding_key`)。 * **典型应用场景:** 点对点精确路由。例如: * 将特定订单ID的状态更新消息路由到只处理该订单的队列。 * 根据日志级别(如 `error`, `info`, `debug`)将日志消息路由到不同的处理队列[^1][^2][^4]。 * **示意图:** ``` 生产者 (Message RK: 'order.update.123') -> [Direct Exchange] -> 队列1 (Binding Key: 'order.update.123') // 匹配,接收消息 -> 队列2 (Binding Key: 'order.create') // 不匹配,忽略 ``` 2. **扇型交换机 (Fanout Exchange)** * **工作原理:** 这种交换机忽略消息的 Routing Key 和队列的 Binding Key。它像广播一样,将收到的**所有**消息**无条件地**路由到所有绑定到它身上的队列。 * **路由键匹配规则:** 无匹配要求(忽略路由键)。 * **典型应用场景:** 发布/订阅模式。需要将同一消息广播给多个消费者或服务。例如: * 系统全局通知(如系统维护公告)。 * 新用户注册成功后,同时通知积分服务、邮件服务、推荐服务等[^1][^2][^4]。 * **示意图:** ``` 生产者 (Message RK: 任意值) -> [Fanout Exchange] -> 队列A (Binding Key: 任意值) // 接收消息 -> 队列B (Binding Key: 任意值) // 接收消息 -> 队列C (Binding Key: 任意值) // 接收消息 ``` 3. **主题交换机 (Topic Exchange)** * **工作原理:** 这是功能最强大、最灵活的交换机类型。它允许使用通配符进行模式匹配。队列的 Binding Key 是一个包含点号 `.` 分隔单词的模式(如 `stock.usd.nyse`, `weather.#`, `*.alert`)。消息的 Routing Key 也是一个点号分隔的单词字符串。交换机根据通配符规则将消息路由到匹配的队列。 * **路由键匹配规则:** 使用通配符模式匹配: * `*` (星号):匹配**一个**单词(例如 `*.orange` 匹配 `a.orange` 但不匹配 `a.b.orange`)。 * `#` (井号):匹配**零个或多个**单词(例如 `lazy.#` 匹配 `lazy`, `lazy.fox`, `lazy.fox.jumps` 等)。 * **典型应用场景:** 需要基于多个维度或属性进行灵活路由的场景。例如: * 新闻分发(按地区 `us.news`、按类别 `sports.#`、按紧急程度 `*.critical`)。 * 设备监控(按设备类型 `sensor.temperature.*`、按地理位置 `europe.#.alert`)[^1][^2][^4][^5]。 * **示意图:** ``` 生产者 (Message RK: 'weather.us.ny.alert') -> [Topic Exchange] -> 队列X (Binding Key: '*.ny.*') // 匹配(第2个单词是'ny') -> 队列Y (Binding Key: 'weather.#') // 匹配(以'weather'开头) -> 队列Z (Binding Key: '*.critical') // 不匹配(最后一个单词是'alert',不是'critical') ``` 4. **头交换机 (Headers Exchange)** * **工作原理:** 这种交换机不依赖于 Routing Key 进行路由,而是依赖于消息的**头信息(Headers)**。队列在绑定时会指定一组键值对(Headers)作为匹配条件。交换机将消息的头信息与绑定的头信息进行比较,根据绑定时设置的匹配规则(`x-match` 参数)来决定是否路由。 * **路由键匹配规则:** 忽略 Routing Key,基于消息头(Headers)匹配: * `x-match = all`:消息头必须包含绑定中指定的**所有**键值对(值也必须相等)。 * `x-match = any`:消息头只要包含绑定中指定的**任意一个**键值对(值相等)即可。 * **典型应用场景:** 基于消息属性(而非路由键)的复杂路由,或者路由键难以表达的多维路由需求。例如: * 根据消息格式(`format=json`)、协议版本(`version=2.0`)等属性路由。 * 需要同时满足多个条件(如 `priority=high` AND `department=finance`)才路由的场景[^1][^2][^4]。 * **示意图 (x-match=all):** ``` 生产者 (Message Headers: {format: 'json', priority: 'high'}) -> [Headers Exchange] -> 队列M (Binding Args: x-match=all, format=json, priority=high) // 匹配(满足所有条件) -> 队列N (Binding Args: x-match=all, format=xml) // 不匹配(缺少priority=high) ``` **总结与选择建议:** * **Direct:** 需要精确路由时使用(一对一)。 * **Fanout:** 需要广播消息时使用(一对所有)。 * **Topic:** 需要基于多个维度进行灵活模式匹配路由时使用(一对多,模式匹配)。 * **Headers:** 需要基于消息属性(头信息)进行复杂逻辑匹配路由时使用(一对多,属性匹配)。虽然功能强大,但因为性能通常不如 Topic Exchange 且使用相对复杂,在常规 Routing Key 能满足需求的情况下,Topic Exchange 是更常用和推荐的选择。 理解这交换机的特性和适用场景是设计和实现高效、灵活 RabbitMQ 消息路由的关键[^1][^2][^4][^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值