微服务学习(4):RabbitMQ中交换机的应用

在RabbitMQ中,交换机(Exchange)扮演着核心角色,负责接收消息并将其路由到一个或多个队列。它根据预定义的规则(如消息的路由键和交换机类型)决定消息的去向,从而实现生产者与消费者之间的解耦。交换机的主要地位体现在其灵活的消息路由能力上,支持多种模式包括直接、主题、扇形和头交换等,适应不同的业务场景需求。

交换机的使用范围广泛,适用于需要实现复杂消息路由逻辑的应用中,比如发布/订阅模式、工作队列、以及基于内容的路由等。通过合理配置不同类型的交换机,可以轻松构建起高效、可靠的消息传递系统,支持异步通信、事件驱动架构及微服务间的交互,极大地增强了系统的可扩展性和灵活性。

总体架构

交换机类型

Fanout交换机

Fanout交换机的介绍

Fanout交换机的实现

Direct交换机

Direct交换机的介绍

Direct交换机的实现

Topic交换机

Topic交换机的介绍

Topic交换机的实现


总体架构

Publisher:生产者,不再发送消息到队列中,而是发给交换机

Exchange:交换机,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。

Queue:消息队列,接收消息、缓存消息。不过队列一定要与交换机绑定。

Consumer:消费者,订阅队列,没有变化

Exchange( 交换机 )只负责转发消息,不具备存储消息的能力 ,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

交换机类型

交换机的类型有四种:

Fanout:广播,将消息交给所有绑定到交换机的队列。我们最早在控制台使用的正是Fanout交换机

Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列

Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符

Headers:头匹配,基于MQ的消息头匹配,用的较少。

Fanout交换机

Fanout交换机的介绍

Fanout在MQ中你可以连接为广播。

在广播模式下,消息发送流程是这样的:

实现要求:

可以有多个队列

每个队列都要绑定到Exchange(交换机)

生产者发送的消息,只能发送到交换机

交换机把消息发送给绑定过的所有队列

订阅队列的消费者都能拿到消息

Fanout交换机的实现

基于RabbitMQ控制台来创建队列、交换机。但是在实际开发时,队列和交换机是程序员定义的,将来项目上线,又要交给运维去创建。那么程序员就需要把程序中运行的所有队列和交换机都写下来,交给运维。在这个过程中是很容易出现错误的。所以,我们采用程序创建队列与检查交换机。

SpringAMQP提供了一个Queue类,用来创建队列,SpringAMQP还提供了一个Exchange接口,来表示所有不同类型的交换机。

1.基于Bean声明队列和交换机

创建一个名为 user.fanout的交换机,类型是Fanout

创建两个队列fanout.queue1fanout.queue2,绑定到交换机user.fanout

实现代码 :(创建一个配置类)

java 复制代码
package com.itheima.consumer.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FanoutConfiguration {

    /**
     * 创建一个Fanout类型的交换机
     * @return
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return ExchangeBuilder.fanoutExchange("user.fanout").build();
    }

    /**
     * 创建一个队列
     * @return
     */
    @Bean
    public Queue fanoutQueue1() {
        return QueueBuilder.durable("fanout.queue1").build();
    }

    /**
     * 绑定队列和交换机
     * @param fanoutQueue1
     * @param fanoutExchange
     * @return
     */
    @Bean
    public Binding fanoutQueue1Binding(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    /**
     * 创建一个队列
     * @return
     */
    @Bean
    public Queue fanoutQueue2() {
        return QueueBuilder.durable("fanout.queue2").build();
    }

    /**
     * 绑定队列和交换机
     * @param fanoutQueue2
     * @param fanoutExchange
     * @return
     */
    @Bean
    public Binding fanoutQueue2Binding(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}

实现效果如下:

user.fanout交换机声明成功:

fanout.queue1 队列和fanout.queue2队列2声明成功:

队列与交换机绑定成功:

2.基于注解声明队列和交换机(推荐)

创建一个名为fanout.exchange的交换机,类型是Fanout

创建两个队列fanout.queue3fanout.queue4,绑定到交换机fanout.exchange

实现代码 :(直接在consumer服务的SpringRabbitListener中的接收方法中使用注解指定)

java 复制代码
  @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "fanout.queue3", durable = "true"),
            exchange = @Exchange(name = "fanout.exchange", type = "fanout"),
            key = "fanout.key"
    ))
    public void listenFanoutQueue1(String msg) {
        System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "fanout.queue4", durable = "true"),
            exchange = @Exchange(name = "fanout.exchange", type = "fanout"),
            key = "fanout.key"
    ))
    public void listenFanoutQueue2(String msg) {
        System.out.println("消费者2接收到消息:【" + msg + "】" + LocalTime.now());
    }

实现效果如下:

交换机声明成功:

队列声明成功:

队列与交换机绑定成功:

基于Bean声明队列和交换机的优势与劣势:

优势方面,基于Bean声明的方式提供了高度的灵活性和控制力 ,允许开发者通过编写具体的Java代码来定义队列、交换机及其绑定关系。这种方式非常适合需要针对特定业务逻辑进行复杂配置的场景,因为它可以轻松实现动态调整和细粒度控制,同时也便于进行单元测试和调试。

劣势在于,这种方法往往需要编写较多的样板代码,增加了项目的初始开发工作量和维护成本。由于所有的配置都需要在代码中明确指定,因此当配置信息发生变更时,可能需要对代码进行修改并重新编译部署,这在一定程度上降低了开发效率,并且对于不熟悉底层机制的开发者来说,学习曲线也相对较高。

基于注解声明队列和交换机的优势与劣势:

优势方面,基于注解的方法以其简洁性和直观性 著称,能够让开发者直接在类或方法上添加必要的注解来声明队列和交换机,大大简化了配置流程,减少了配置文件的数量和复杂度。这种方式不仅提升了代码的可读性 ,还使得项目结构更加清晰,特别适合于配置较为简单固定的场景。

劣势是,虽然注解方式简化了配置,但它在灵活性上不如基于Bean声明的方式。例如,复杂的动态配置或条件性配置可能难以仅通过注解实现,限制了其适用范围。此外,对于一些高级功能或非标准配置需求,注解可能无法提供足够的支持,导致需要额外的配置或其他方式来弥补不足。

Direct交换机

Direct交换机的介绍

在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。

在Direct模型下:

队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)

消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey

Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息

需求:

声明一个名为user.direct的交换机

声明队列direct.queue1,绑定user.directbindingKeybludred

声明队列direct.queue2,绑定user.directbindingKeyyellowred

consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2

在publisher中编写测试方法,向user.direct发送消息(比较简单,在测试类中编写方法,然后在接收方法中配置注解属性即可,这里就不实现了)

Direct交换机的实现

基于注解方式声明队列与交换机

实现代码:(直接在consumer服务的SpringRabbitListener中的接收方法中使用注解指定)

java 复制代码
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1", durable = "true"),
            exchange = @Exchange(name = "user.direct", type = "direct"),
            key = {"red","blue"}
    ))
    public void listenDirectQueue1(String msg) {
        System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
    }


    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2", durable = "true"),
            exchange = @Exchange(name = "user.direct", type = "direct"),
            key = {"red","green"}
    ))
    public void listenDirectQueue2(String msg) {
        System.out.println("消费者2接收到消息:【" + msg + "】" + LocalTime.now());
    }

实现效果如下:

交换机声明成功:

队列声明成功:

队列与交换机绑定成功:

Topic交换机

Topic交换机的介绍

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。

只不过Topic类型Exchange可以让队列在绑定BindingKey 的时候使用通配符!

BindingKey 一般都是有一个或多个单词组成,多个单词之间以.分割,例如: item.insert

通配符规则:

#:匹配一个或多个词

*:匹配不多不少恰好1个词

举例:

item.#:能够匹配item.spu.insert 或者 item.spu

item.*:只能匹配item.spu

Topic交换机的实现

基于注解方式声明队列与交换机

实现代码:(直接在consumer服务的SpringRabbitListener中的接收方法中使用注解指定)

java 复制代码
  /**
     * topic模式
     * @param msg
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue1"),
            exchange = @Exchange(name = "user.topic", type = ExchangeTypes.TOPIC),
            key = "china.#"
    ))
    public void listenTopicQueue1(String msg){
        System.out.println("消费者1接收到topic.queue1的消息:【" + msg + "】");
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue2"),
            exchange = @Exchange(name = "user.topic", type = ExchangeTypes.TOPIC),
            key = "#.news"
    ))
    public void listenTopicQueue2(String msg){
        System.out.println("消费者2接收到topic.queue2的消息:【" + msg + "】");
    }

实现效果如下:

交换机声明成功:

队列声明成功:

队列与交换机绑定成功:

相关推荐
爱上妖精的尾巴6 分钟前
3-7 WPS JS宏 工作表移动复制实例-2(多工作簿的多工作表合并)学习笔记
javascript·笔记·学习·wps·js宏·jsa
啊迷诺斯10 分钟前
虚拟机ip设置
linux·运维·服务器
不会飞的小龙人13 分钟前
Docker安装Redpandata-console控制台
docker·容器·portainer·console·redpandata
不会飞的小龙人15 分钟前
Docker安装Postgres_16数据库
数据库·docker·postgresql·容器·portainer
大G哥24 分钟前
jenkins集成docker发布java项目
java·运维·开发语言·docker·jenkins
rider1892 小时前
Java多线程及线程变量学习:从熟悉到实战(下)
java·开发语言·学习
贩卖纯净水.2 小时前
React高级内容探索
前端·学习·react.js·前端框架
飞翔沫沫情2 小时前
华为 VRP 系统简介&配置SSH,TELNET远程登录
运维·华为·hcip·数通
谢宁华为战略管理研发管理2 小时前
《向华为学习:BEM战略解码》课程大纲
学习·华为
gordon~92 小时前
面试题-微服务
微服务·云原生·架构