RabbitMQ实战之三种交换机(Direct、Topic和Fanout)

本文介绍了RabbitMQ中三种主要的交换机类型:DirectExchange、FanoutExchange和TopicExchange。DirectExchange基于路由键进行消息投递;FanoutExchange将消息广播到所有绑定的队列;TopicExchange则使用通配符规则进行路由。通过代码示例展示了如何在Spring Boot应用中配置和使用这些交换机,以及对应的生产者和消费者。测试结果显示了不同交换机模式下的消息传递行为。

一、消息推送到接收的流程图

首先先介绍一个简单的一个消息推送到接收的流程,提供一个简单的图:
在这里插入图片描述
黄色的圈圈就是我们的消息推送服务,将消息推送到 中间方框里面也就是 rabbitMq的服务器,然后经过服务器里面的交换机、队列等各种关系(后面会详细讲)将数据处理入列后,最终右边的蓝色圈圈消费者获取对应监听的消息。

常用的交换机有以下三种,因为消费者是从队列获取信息的,队列是绑定交换机的(一般),所以对应的消息推送/接收模式也会有以下几种:

1.Direct Exchange

直连型交换机,根据消息携带的路由键将消息投递给对应队列。
大致流程,有一个队列绑定到一个直连交换机上,同时赋予一个路由键 routing key 。
然后当一个消息携带着路由值为X,这个消息通过生产者发送给交换机时,交换机就会根据这个路由值X去寻找绑定值也是X的队列。

2.Fanout Exchange

扇型交换机,这个交换机没有路由键概念,就算你绑了路由键也是无视的。 这个交换机在接收到消息后,会直接转发到绑定到它上面的所有队列。

3.Topic Exchange

主题交换机,这个交换机其实跟直连交换机流程差不多,但是它的特点就是在它的路由键和绑定键之间是有规则的。
简单地介绍下规则:
*(星号) 用来表示一个单词 (必须出现的)
#(井号) 用来表示任意数量(零个或多个)单词
通配的绑定键是跟队列进行绑定的,举例如下:
队列Q1 绑定键为 .TT.
队列Q2绑定键为 TT.#
如果一条消息携带的路由键为 A.TT.B,那么队列Q1将会收到;
如果一条消息携带的路由键为TT.AA.BB,那么队列Q2将会收到;

二、Direct Exchange

直连交换机中,每对交换机和队列之间只能通过唯一一个路由键来绑定(当然,每个交换机可绑定多个队列,每个队列也可绑定多个交换机)。

1.DirectConfig.java

package com.springboot.rabbit.demo.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DirectConfig {
    //用@Bean注解配置好需要注册到RabbitMQ服务器的消息队列。项目启动时,这些队列就会被注册。
	//队列 起名:com.direct.queue.1
    @Bean
    public Queue directQueue1() {
        // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
        // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
        // 比如return new Queue("TestDirectQueue",true,true,false);
        return new Queue("com.direct.queue.1");
    }

    @Bean
    public Queue directQueue2() {
        return new Queue("com.direct.queue.2");
    }

    @Bean
    public Queue directQueue3() {
        return new Queue("com.direct.queue.3");
    }

    //Direct交换机 起名:com.direct.exchange
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange("com.direct.exchange");
    }
    
    //绑定  将队列和交换机绑定, 并设置用于匹配键:com.direct.routingKey.1
    @Bean
    public Binding bindQueue1() {
        return BindingBuilder.bind(directQueue1()).to(directExchange()).with("com.direct.routingKey.1");
    }

    @Bean
    public Binding bindQueue2() {
        return BindingBuilder.bind(directQueue2()).to(directExchange()).with("com.direct.routingKey.2");
    }

    @Bean
    public Binding bindQueue3() {
        return BindingBuilder.bind(directQueue3()).to(directExchange()).with("com.direct.routingKey.3");
    }
}

2.生产者DirectProducer.java

package com.springboot.rabbit.demo.producer;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class DirectProducer {
	
	//使用RabbitTemplate,这提供了接收/发送等等方法
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send1(String msg) {
    	//将消息携带绑定键值:com.direct.routingKey.1 发送到交换机com.direct.exchange
        rabbitTemplate.convertAndSend("com.direct.exchange", "com.direct.routingKey.1", msg);
    }

    public void send2(String msg) {
        rabbitTemplate.convertAndSend("com.direct.exchange", "com.direct.routingKey.2", msg);
    }

    public void send3(String msg) {
        rabbitTemplate.convertAndSend("com.direct.exchange", "com.direct.routingKey.3", msg);
    }
}

3.消费者DirectConsumer.java

package com.springboot.rabbit.demo.consumer;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(queues = {"com.direct.queue.1", "com.direct.queue.2", "com.direct.queue.3"})
//监听的队列名称 com.direct.queue.1\2\3
public class DirectConsumer {
    @RabbitHandler
    public void process(String msg) {
       System.out.println(msg);
    }
}

4.业务控制层DirectController.java

package com.springboot.rabbit.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springboot.rabbit.demo.producer.DirectProducer;
@RestController
@RequestMapping("direct")
public class DirectController {
    @Autowired
    private DirectProducer directProducer;

    @RequestMapping("1")
    public void produce1(String msg) {
        directProducer.send1(msg);
    }

    @RequestMapping("2")
    public void produce2(String msg) {
        directProducer.send2(msg);
    }

    @RequestMapping("3")
    public void produce3(String msg) {
        directProducer.send3(msg);
    }
}

5.测试结果

启动RabbitService,然后运行RabbitDemoApplication.java主类,在浏览器中访问如下链接,进行测试:
在这里插入图片描述
在这里插入图片描述
详细信息在http://localhost:15672/#/中即可查看
在这里插入图片描述

三、Fanout Exchange

扇出交换机无需绑定路由,只要是生产者发送到扇出交换机上的消息全部都会被消费者监听到并消费。所以,随意发送任意路由都可以。

1.FanoutConfig.java

package com.springboot.rabbit.demo.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutConfig {

    @Bean
    public Queue fanoutQueue1() {
        return new Queue("com.fanout.queue.1");
    }

    @Bean
    public Queue fanoutQueue2() {
        return new Queue("com.fanout.queue.2");
    }

    @Bean
    public Queue fanoutQueue3() {
        return new Queue("com.fanout.queue.3");
    }

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("com.fanout.exchange");
    }

    @Bean
    public Binding bindFanoutQueue1() {
        return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange());
    }

    @Bean
    public Binding bindFanoutQueue2() {
        return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange());
    }

    @Bean
    public Binding bindFanoutQueue3() {
        return BindingBuilder.bind(fanoutQueue3()).to(fanoutExchange());
    }
}

2.生产者FanoutProducer.java

package com.springboot.rabbit.demo.producer;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class FanoutProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    public void send(String msg) {
        rabbitTemplate.convertAndSend("com.fanout.exchange", "whatever routingKey", msg);
    }
}

3.消费者FanoutConsumer.java

package com.springboot.rabbit.demo.consumer;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(queues = {"com.fanout.queue.1", "com.fanout.queue.2", "com.fanout.queue.3"})
public class FanoutConsumer {
    @RabbitHandler
    public void process(String msg) {
    	System.out.println(msg);
    }
}

4.业务控制层FanoutController.java

package com.springboot.rabbit.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springboot.rabbit.demo.producer.FanoutProducer;
@RestController
@RequestMapping("fanout")
public class FanoutController {
    @Autowired
    private FanoutProducer fanoutProducer;
    @RequestMapping("produce")
    public void produce(String msg) {
        fanoutProducer.send(msg);
    }
}

5.测试结果

测试结果如下:
在这里插入图片描述
在这里插入图片描述

四、Topic Exchange

Topic Exchange转发消息主要是根据通配符。在这种交换机下,队列和交换机的绑定会定义一种路由模式,那么,通配符就要在这种路由模式和路由键之间匹配后交换机才能转发消息。
在这种交换机模式下:
(1)路由键(Routing Key)命名必须为一串字符,用句号(.) 隔开,比如 jake.topic.queue。
(2)队列和交换机通过路由键绑定。

1.TopicConfig.java

package com.springboot.rabbit.demo.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TopicConfig {

    @Bean
    public Queue topicQueueAccurate() {
        return new Queue("com.topic.queue.accurate");
    }

    @Bean
    public Queue topicQueueSingle() {
        return new Queue("com.topic.queue.single");
    }

    @Bean
    public Queue topicQueueAny() {
        return new Queue("com.topic.queue.any");
    }

    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange("com.topic.exchange");
    }

    @Bean
    public Binding bindWithAccurateMatcher() {
        return BindingBuilder.bind(topicQueueAccurate()).to(topicExchange()).
                with("com.topic.routingKey.accurate");
    }

    @Bean
    public Binding bindWithSingleWordMatcher() {
    	// * (星号) 用来表示一个单词 (必须出现的)
        return BindingBuilder.bind(topicQueueSingle()).to(topicExchange()).
                with("com.topic.routingKey.*");
    }

    @Bean
    public Binding bindWithAnyWordMatcher() {
    	//  # (井号) 用来表示任意数量(零个或多个)单词
        return BindingBuilder.bind(topicQueueAny()).to(topicExchange()).
                with("com.topic.#");
    }

}

2.生产者TopicProducer.java

package com.springboot.rabbit.demo.producer;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TopicProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendAccurate(String msg) {
        rabbitTemplate.convertAndSend("com.topic.exchange", "com.topic.routingKey.accurate", msg);
    }

    public void sendSingle(String msg) {
        rabbitTemplate.convertAndSend("com.topic.exchange", "com.topic.routingKey.only-one-word", msg);
    }

    public void sendAny(String msg) {
        rabbitTemplate.convertAndSend("com.topic.exchange", "com.topic.routingKey.as.much.as.you.want", msg);
    }
}

3.消费者

精确匹配TopicAccurateMatchConsumer.java

package com.springboot.rabbit.demo.consumer;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = {"com.topic.queue.accurate"})
public class TopicAccurateMatchConsumer {
    @RabbitHandler
    public void process(String msg) {
    	System.out.println(msg);
    }
}

匹配所有TopicAnyMatchConsumer.java

package com.springboot.rabbit.demo.consumer;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = {"com.topic.queue.any"})
public class TopicAnyMatchConsumer {
    @RabbitHandler
    public void process(String msg) {
    	System.out.println(msg);
    }
}

匹配单一队列TopicSingleMatchConsumer.java

package com.springboot.rabbit.demo.consumer;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = {"com.topic.queue.single"})
public class TopicSingleMatchConsumer {
    @RabbitHandler
    public void process(String msg) {
    	System.out.println(msg);
    }
}

4.业务控制层FanoutController.java

package com.springboot.rabbit.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springboot.rabbit.demo.producer.TopicProducer;
@RestController
@RequestMapping("topic")
public class TopicController {
    @Autowired
    private TopicProducer topicProducer;

    @RequestMapping("accurate")
    public void accurate(String msg) {
        topicProducer.sendAccurate(msg);
    }

    @RequestMapping("single")
    public void single(String msg) {
        topicProducer.sendSingle(msg);
    }

    @RequestMapping("any")
    public void any(String msg) {
        topicProducer.sendAny(msg);
    }
}

5.测试结果

精确匹配测试结果:
在这里插入图片描述
在这里插入图片描述
任意匹配测试结果:
在这里插入图片描述
在这里插入图片描述
单一匹配测试结果:
在这里插入图片描述
在这里插入图片描述

五、项目结构

1.application.yml配置

目的是自由切换不同的yml配置文件,切换项目环境(dev、prod、test)等。
application.yml

spring:
  profiles:
    active: dev

application-dev.yml

server:
  port: 8086

2.项目目录结构

在这里插入图片描述
以上整体项目代码链接如下
RabbitMQ

代码思路参考了SpringBoot + RabbitMQ实战之通过代码熟悉三种交换机(Direct、Topic和Fanout)
特此对这位博主表示感谢!

参考文章
https://blog.youkuaiyun.com/qq_15329947/article/details/86528854
https://blog.youkuaiyun.com/qq_35387940/article/details/100514134

<think>我们首先需要解释三种交换机的区别,然后说明它们各自的使用场景,并给出代码示例(使用pika库)来演示如何声明绑定这些交换机。###1.DirectExchange(直连交换机)-**工作方式**:根据消息的routingkey精确匹配队列的bindingkey。只有当routingkey完全匹配时,消息才会被路由到该队列。-**使用场景**:适用于消息的单播路由(Unicast)。例如,将不同的任务类型(如:'image.resize','video.encode')分发到不同的处理队列。###2.TopicExchange(主题交换机)-**工作方式**:根据routingkeybindingkey的模式匹配进行路由。bindingkey可以使用通配符:-`*`(星号)代替一个单词-`#`(井号)代替零个或多个单词-**使用场景**:适用于消息的多播路由(Multicast),且需要根据多个条件进行灵活匹配的场景。例如,日志系统根据日志级别来源进行分发(如:'log.error.system'可以匹配到bindingkey为'log.error.*'或'log.#'的队列)。###3.FanoutExchange(扇出交换机)-**工作方式**:将消息广播到所有绑定到该交换机队列,忽略routingkey。-**使用场景**:适用于消息的广播(Broadcast)。例如,一个用户注册后需要同时发送欢迎邮件创建用户数据目录,这两个任务可以分别由不同的队列处理。###代码示例(使用Pythonpika库)####声明交换机```pythonimportpikaconnection=pika.BlockingConnection(pika.ConnectionParameters('localhost'))channel=connection.channel()#声明三种类型的交换机channel.exchange_declare(exchange='direct_logs',exchange_type='direct')channel.exchange_declare(exchange='topic_logs',exchange_type='topic')channel.exchange_declare(exchange='fanout_logs',exchange_type='fanout')```####绑定队列与发送消息示例#####1.DirectExchange示例```python#生产者:发送消息到direct_logs交换机,指定routing_keychannel.basic_publish(exchange='direct_logs',routing_key='error',body='Errorlogmessage')#消费者:声明队列并绑定到direct_logs交换机,指定binding_key为'error'channel.queue_declare(queue='error_logs_queue')channel.queue_bind(exchange='direct_logs',queue='error_logs_queue',routing_key='error')#只有指定routing_key为'error'的消息会被路由到error_logs_queue```#####2.TopicExchange示例```python#生产者:发送消息到topic_logs交换机,指定不同的routing_keychannel.basic_publish(exchange='topic_logs',routing_key='auth.error',body='AuthError')channel.basic_publish(exchange='topic_logs',routing_key='system.warning',body='SystemWarning')#消费者1:绑定所有error级别的日志channel.queue_declare(queue='all_error_logs')channel.queue_bind(exchange='topic_logs',queue='all_error_logs',routing_key='*.error')#该队列会收到routing_key为'auth.error''app.error'的消息,但不会收到'system.warning'#消费者2:绑定所有系统相关的日志channel.queue_declare(queue='system_logs')channel.queue_bind(exchange='topic_logs',queue='system_logs',routing_key='system.*')#该队列会收到routing_key为'system.warning''system.error'的消息```#####3.FanoutExchange示例```python#生产者:发送消息到fanout_logs交换机,routing_key被忽略channel.basic_publish(exchange='fanout_logs',routing_key='',body='Broadcastmessage')#消费者:任意绑定的队列都会收到消息channel.queue_declare(queue='mail_queue')channel.queue_bind(exchange='fanout_logs',queue='mail_queue')channel.queue_declare(queue='storage_queue')channel.queue_bind(exchange='fanout_logs',queue='storage_queue')#两个队列都会收到同样的消息```###总结对比表|交换机类型|路由规则|典型应用场景||--------------|------------------------------------|--------------------------------||Direct|精确匹配routingkeybindingkey|单播,点对点消息(如任务分发)||Topic|通配符模式匹配|多播,多条件灵活匹配(如日志分类)||Fanout|广播,忽略routingkey|广播(如事件通知)|###相关问题§§相关问题§§:1.RabbitMQ中Headers交换机的原理使用场景是什么?2.在RabbitMQ中如何实现消息的延迟队列?3.RabbitMQ的集群如何搭建?有哪些需要注意的配置?4.如何确保RabbitMQ在消息处理失败时进行重试?</think> ### RabbitMQDirectTopicFanout交换机的区别及使用场景 在RabbitMQ中,交换机(exchange)是消息路由的核心组件,决定了消息如何从生产者路由到队列。以下是三种主要交换机的对比: | 交换机类型 | 路由机制 | 使用场景 | 特点 | |-----------|---------|---------|------| | **Direct** | 精确匹配routing key | 点对点精准投递 | 1:1精确路由 | | **Topic** | routing key模式匹配 | 多条件复杂路由 | 支持通配符 | | **Fanout** | 无路由逻辑 | 广播消息 | 1:N广播 | --- ### 1. Direct Exchange (直连交换机) **原理**:将消息路由到与消息的routing key**完全匹配**的队列 **适用场景**: - 精确的任务分发(如根据消息类型路由) - 需要1:1精确路由的场景 - 日志系统的不同级别处理(error/info/warn) ```python # 生产者 - 发送到指定routing key的队列 channel.exchange_declare(exchange='direct_logs', exchange_type='direct') channel.basic_publish( exchange='direct_logs', routing_key='error', # 精确匹配 body='ERROR: Something failed!' ) # 消费者 - 绑定特定key的队列 channel.queue_declare(queue='error_queue') channel.queue_bind( exchange='direct_logs', queue='error_queue', routing_key='error' # 只接收error消息 ) ``` --- ### 2. Topic Exchange (主题交换机) **原理**:使用通配符匹配模式,支持`*`(单单词)`#`(多单词)通配符 **适用场景**: - 多维度消息路由(如地区+产品类别) - 复杂条件匹配 - 动态订阅系统 ```python # 生产者 - 发送带复杂routing key的消息 channel.exchange_declare(exchange='topic_orders', exchange_type='topic') channel.basic_publish( exchange='topic_orders', routing_key='us.electronics.order', # 可匹配*.order body='New US electronics order' ) # 消费者1 - 接收所有电子订单 channel.queue_bind( exchange='topic_orders', queue='electronics_q', routing_key='*.electronics.order' # 匹配所有地区的电子订单 ) # 消费者2 - 接收所有订单 channel.queue_bind( exchange='topic_orders', queue='all_orders_q', routing_key='#.order' # #匹配任意多级路由 ) ``` --- ### 3. Fanout Exchange (扇出交换机) **原理**:忽略routing key,将消息广播到所有绑定的队列 **适用场景**: - 事件广播(如系统配置更新) - 状态同步 - 多消费者并行处理 ```python # 生产者 - 广播消息 channel.exchange_declare(exchange='config_updates', exchange_type='fanout') channel.basic_publish( exchange='config_updates', routing_key='', # 被忽略 body='Config update: v2.0' ) # 消费者1 - 缓存服务 channel.queue_bind(exchange='config_updates', queue='cache_update_q') # 消费者2 - 日志服务 channel.queue_bind(exchange='config_updates', queue='log_update_q') ``` --- ### 交换机类型选择流程图 ``` +----------------+ | 需要广播所有队列? | +-------+--------+ | +--------------+-------------+ | | +---------v---------+ +----------v---------+ | 路由条件是否复杂匹配? | | 使用Fanout Exchange | +---------+---------+ +---------------------+ | +---------v---------+ | 需要精确键值匹配? | +---------+---------+ | +-----------v-----------+ | 是:Direct / 否:Topic | +-----------------------+ ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值