#RabbitMQ# 消息队列入门

目录

[一 MQ技术选型](#一 MQ技术选型)

[1 运行rabbitmq](#1 运行rabbitmq)

[2 基本介绍](#2 基本介绍)

[3 快速入门](#3 快速入门)

[1 交换机负责路由消息给队列](#1 交换机负责路由消息给队列)

[2 数据隔离](#2 数据隔离)

[二 Java客户端](#二 Java客户端)

[1 快速入门](#1 快速入门)

[2 WorkQueue](#2 WorkQueue)

[3 FanOut交换机](#3 FanOut交换机)

[4 Direct交换机](#4 Direct交换机)

[5 Topic交换机](#5 Topic交换机)

[*6 声明队列交换机](#*6 声明队列交换机)

[1 在配置类当中声明](#1 在配置类当中声明)

[2 使用注解的方式指定](#2 使用注解的方式指定)

[7 消息转换器](#7 消息转换器)


*前景引入

维度 异步通讯 同步通讯 RabbitMQ 的定位
交互方式 通过中间件间接通信,无阻塞等待 直接通信,需实时响应 作为异步通讯的核心载体,支持消息缓存与路由
耦合度 低(生产者和消费者解耦) 高(调用方依赖被调用方可用性) 通过队列解耦系统,提升容错性
适用场景 高并发、耗时任务、事件驱动架构 实时性要求高的简单交互 天然适合异步场景,也可通过 RPC 支持同步需求
性能与扩展性 高吞吐,支持水平扩展 受限于实时响应能力 通过集群、负载均衡优化异步性能

一 MQ技术选型

MQ(message Queue)消息队列,字面来看就是存放消息的队列。也就是异步调用中的Broke。

1 运行rabbitmq

在虚拟机上安装Docker_虚拟机安装docker-CSDN博客

拉取镜像

  • docker pull rabbitmq:3-management

在容器当中运行

  • docker run ...

借助端口访问

2 基本介绍

核心概念总结

角色 作用 类比
Publisher 发送消息的程序 寄信人
Exchange 按规则将消息分发到队列 邮局分拣员
Queue 存储消息的容器 邮箱
Consumer 从队列取消息并处理的程序 收信人
Virtual Host 隔离不同业务的消息环境(如测试、生产) 邮局内的独立部门

3 快速入门

1 交换机负责路由消息给队列

添加成功

找到一台交换机

需要添加绑定队列从而实现路由给队列

消息路由成功

2 数据隔离

RabbitMQ 中的 虚拟主机(vhost) 可以用一个简单的比喻来理解:它就像一台大型服务器中的"独立房间",每个房间都有自己的门禁系统、家具和规则,互不干扰。以下是它的核心作用:

实现:

先添加一个用户

现在这个用户还没有虚拟主机,这里其是无法访问之前创建的队列,是与之前的虚拟主机隔离开的

现在退出原先的用户,以刚刚创建的用户信息登录,然后添加一个虚拟主机

现在就可以在现在的用户之下的虚拟主机上创建新的队列

二 Java客户端

1 快速入门

实现:

1 导入spring-amqp依赖

XML 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

2 添加队列

3 配置MQ地址

4 发送消息

java 复制代码
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    void testSendMessage2Queue() {
        String queueName = "simple.queue1";
        String msg = "hello, amqp!";
        rabbitTemplate.convertAndSend(queueName, msg);
    }

5 队列

6 在消费者的相关方法中定义

java 复制代码
    @RabbitListener(queues = "simple.queue1")
    public void listenSimpleQueue(String msg) {
        System.out.println("消费者收到了simple.queue的消息:【" + msg + "】");
    }

7 然后将项目启动,再在测试类中发送消息,控制台会实时监控到发送的消息

8 队列当中的消息拿出来在控制台里面就没有消息了

2 WorkQueue

任务模型:简单来说就是让多个消费者绑定到一个队列,共同消费队列当中的消息。

一个队列多个消费者,可以缓解消息堆积问题。

1 配置项

2 不写的话(默认一人一半,处理不完在队列里等待)

3 新增一个队列

4 两个消费者(消费能力不同,消费能力相同应该是轮询消费)

java 复制代码
    @RabbitListener(queues = "work.queue")
    public void listenWorkQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1 收到了 work.queue的消息:【" + msg + "】");
        Thread.sleep(20);
    }

    @RabbitListener(queues = "work.queue")
    public void listenWorkQueue2(String msg) throws InterruptedException {
        System.err.println("消费者2 收到了 work.queue的消息...... :【" + msg + "】");
        Thread.sleep(200);
    }

5 生产者

java 复制代码
    @Test
    void testWorkQueue() throws InterruptedException {
        String queueName = "work.queue";
        for (int i = 1; i <= 50; i++) {
            String msg = "hello, worker, message_" + i;
            rabbitTemplate.convertAndSend(queueName, msg);
            Thread.sleep(20);
        }
    }

6 测试

3 FanOut交换机

真正生产环境都会经过exchange来发送消息,而不是直接发送到队列,交换机的类型有以下三种

Fanout模式会将接受到的消息广播到跟其绑定的每一个队列,广播模式。

例子

1 先将队列声明好

2 再声明交换机同时与队列绑定

3 消费者

java 复制代码
    @RabbitListener(queues = "fanout.queue1")
    public void listenFanoutQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1 收到了 fanout.queue1的消息:【" + msg + "】");
    }

    @RabbitListener(queues = "fanout.queue2")
    public void listenFanoutQueue2(String msg) throws InterruptedException {
        System.out.println("消费者2 收到了 fanout.queue2的消息:【" + msg + "】");
    }

4 生产者

java 复制代码
    @Test
    void testSendFanout() {
        String exchangeName = "hmall.fanout";
        String msg = "hello, everyone!";
        rabbitTemplate.convertAndSend(exchangeName, null, msg);
    }

测试结果:

为什么第二个参数是 null

在你的代码中,第二个参数是 null,这是为了配合 Fanout 交换机 的特性。以下是关键点:

Fanout 交换机的特性

  • Fanout 交换机 (也称为广播交换机)会将消息 无条件广播到所有绑定到该交换机的队列完全忽略路由键
  • 因此,在使用 Fanout 交换机时,路由键(routingKey)可以设为 null,因为交换机不会使用它来决定消息的路由规则。

4 Direct交换机

这种交换机可以实现与Fanout交换机相同的效果同时也可以实现定向的效果。

需求

1 创建队列与交换机

(交换机需要给routingKey值)

2 消费者

java 复制代码
@RabbitListener(queues = "direct.queue1") // 直接监听名为 direct.queue1 的队列
public void listenDirectQueue1(String msg) {
    System.out.println("消费者1 收到了 direct.queue1的消息:【" + msg + "】");
}

@RabbitListener(queues = "direct.queue2") // 直接监听名为 direct.queue2 的队列
public void listenDirectQueue2(String msg) {
    System.out.println("消费者2 收到了 direct.queue2的消息:【" + msg + "】");
}

3 生产者

java 复制代码
    @Test
    void testSendDirect() {
        String exchangeName = "hmall.direct";
        String msg = "蓝色通知,警报解除,哥斯拉是放的气球";
        rabbitTemplate.convertAndSend(exchangeName, "blue", msg);
    }

测试:

发送的路由键 接收队列 触发的消费者
red direct.queue1, direct.queue2 消费者1 + 消费者2
blue direct.queue1 消费者1
yellow direct.queue2 消费者2

可以根据需求更改生产者的代码逻辑:

5 Topic交换机

Topic 交换机是 RabbitMQ 中基于模式匹配的路由机制 ,允许通过通配符(*#)实现灵活的路由规则。

需求

实现:

声明队列和交换机

消费者

java 复制代码
    @RabbitListener(queues = "topic.queue1")
    public void listenTopicQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1 收到了 topic.queue1的消息:【" + msg + "】");
    }

    @RabbitListener(queues = "topic.queue2")
    public void listenTopicQueue2(String msg) throws InterruptedException {
        System.out.println("消费者2 收到了 topic.queue2的消息:【" + msg + "】");
    }

生产者

java 复制代码
    @Test
    void testSendTopic() {
        String exchangeName = "hmall.topic";
        String msg = "今天天气挺不错,我的心情的挺好的";
        rabbitTemplate.convertAndSend(exchangeName, "china.weather", msg);
    }

测试:可以根据需求修改发送的RoutingKey

Direct交换机与Topic的差异

特性 Direct 交换机 Topic 交换机
路由键匹配方式 精确匹配(完全一致) 模式匹配(支持通配符 *#
灵活性 低(适合简单路由) 高(适合复杂路由场景)
典型场景 订单状态变更、任务分发 日志分类、多维度消息分发

*6 声明队列交换机

为了改善在控制台创建队列交换机的笨重,可以使用相关接口

声明队列和交换机

实现:

1 在配置类当中声明

Fanout的

java 复制代码
package com.itheima.consumer.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 FanoutConfiguration {

    // fanoutExchange 定义交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        // ExchangeBuilder.fanoutExchange("").build();
        return new FanoutExchange("hmall.fanout2");
    }

    //  queue 创建队列
    @Bean
    public Queue fanoutQueue3(){
        // QueueBuilder.durable("ff").build();//持久化
        return new Queue("fanout.queue3");
    }

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

    // 创建队列
    @Bean
    public Queue fanoutQueue4(){
        return new Queue("fanout.queue4");
    }

    // 绑定队列和交换机
    @Bean
    public Binding fanoutBinding4(){
        return BindingBuilder.bind(fanoutQueue4()).to(fanoutExchange());
    }
}

Direct的

java 复制代码
package com.itheima.consumer.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;

// @Configuration
public class DirectConfiguration {

    //   定义交换机
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange("hmall.direct");
    }

    //   创建队列
    @Bean
    public Queue directQueue1() {
        return new Queue("direct.queue1");
    }
    // 队列与交换机进行绑定
    @Bean
    public Binding directQueue1BindingRed(Queue directQueue1, DirectExchange directExchange) {
        return BindingBuilder.bind(directQueue1).to(directExchange).with("red");
    }

    //  队列与交换机进行绑定
    @Bean
    public Binding directQueue1BindingBlue(Queue directQueue1, DirectExchange directExchange) {
        return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");
    }

    //   创建队列
    @Bean
    public Queue directQueue2() {
        return new Queue("direct.queue2");
    }

    //  队列与交换机进行绑定
    @Bean
    public Binding directQueue2BindingRed(Queue directQueue2, DirectExchange directExchange) {
        return BindingBuilder.bind(directQueue2).to(directExchange).with("red");
    }

    //  队列与交换机进行绑定
    @Bean
    public Binding directQueue2BindingBlue(Queue directQueue2, DirectExchange directExchange) {
        return BindingBuilder.bind(directQueue2).to(directExchange).with("yellow");
    }

}

2 使用注解的方式指定

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

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2", durable = "true"),
            exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "yellow"}
    ))
    public void listenDirectQueue2(String msg) throws InterruptedException {
        System.out.println("消费者2 收到了 direct.queue2的消息:【" + msg + "】");
    }

通过使用 @RabbitListenerbindings + @QueueBinding 注解的方式,不需要手动创建队列、交换机或绑定关系

  1. 检查资源是否存在

    Spring 会通过 RabbitAdmin 组件向 RabbitMQ 服务器发起检查,确认队列、交换机是否已存在。

  2. 自动创建缺失的资源

    • 若队列 direct.queue1direct.queue2 不存在,会根据 @Queue 注解的配置(如 namedurable自动创建队列

    • 若交换机 hmall.direct 不存在,会根据 @Exchange 注解的配置(如 nametype自动创建交换机

  3. 自动绑定队列到交换机

    根据 key 指定的路由键,将队列与交换机绑定(如 direct.queue1 绑定 redblue 路由键)。

7 消息转换器

使用

1. SimpleMessageConverter(默认)

  • 行为

    • 支持 Stringbyte[]Serializable 对象。

    • 若消息是 Serializable 对象,使用 Java 原生序列化。

  • 问题

    • 强耦合:发送方和接收方必须有相同的类路径(否则反序列化失败)。

    • 安全性差:Java 原生序列化易受攻击(如反序列化漏洞)。

2. Jackson2JsonMessageConverter(推荐)

  • 行为

    • 将对象转换为 JSON 字符串,再转为 byte[]

    • 反序列化时,将 JSON 还原为对象(需指定目标类型)。

  • 优势

    • 跨语言兼容:JSON 是通用格式,非 Java 客户端也可解析。

    • 松耦合:不强制要求发送方和接收方的类路径一致。

    • 安全性高:避免 Java 原生序列化漏洞

1 依赖引入

XML 复制代码
        <!--Jackson-->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>

2 Bean的创建

java 复制代码
    // 消息转换器
    @Bean
    public MessageConverter jacksonMessageConvertor(){
        return new Jackson2JsonMessageConverter();
    }

3 消费者

java 复制代码
    @RabbitListener(queues = "object.queue")
    public void listenObject(Map<String, Object> msg) throws InterruptedException {
        System.out.println("消费者 收到了 object.queue的消息:【" + msg + "】");
    }

4 生产者

java 复制代码
    @Test
    void testSendObject() {
        Map<String, Object> msg = new HashMap<>(2);
        msg.put("name", "jack");
        msg.put("age", 21);
        rabbitTemplate.convertAndSend("object.queue", msg);
    }

5 在实际业务当中的使用

相关推荐
回家路上绕了弯1 天前
深入解析Agent Subagent架构:原理、协同逻辑与实战落地指南
分布式·后端
用户8307196840822 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
用户8307196840823 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者5 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者6 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧8 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖8 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农8 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者8 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀8 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式