RabbitMQ讲解-基础篇

RabbitMQ讲解-基础篇

安装部署

第一种方式:通过Podman Desktop(可视化界面) + Podman Machine(windows连接WSL2) + WSL2(虚拟机) + Podman(类似docker的工具)部署RabbotMQ

  1. 下载镜像
  2. 运行容器

第二种方式:通过docker部署

确保虚拟机已经安装好docker后,执行:

bash 复制代码
# 一条命令,先拉取镜像再运行成容器
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management

访问 http://localhost:15672(默认账号密码 guest/guest)进入管理界面

基本概念

RabbitMQ 是一款基于 AMQP(Advanced Message Queuing Protocol)协议的开源消息队列,以其轻量、灵活、支持多种消息模式著称,尤其适合复杂业务场景下的服务解耦和异步通信

引入了新概念

概念 作用
生产者 发送方,需考虑发送失败
消费者 接收方
队列(Queue) 存储消息,需考虑溢出、持久化
交换机(Exchange) 接收生产者发送的消息,根据 "路由规则" 转发到对应的队列;本身不存消息
绑定(Binding) 交换机和队列之间绑定
路由键(Routing Key) 消息的 "地址标识",交换机根据它和绑定规则决定消息发往哪个队列
虚拟主机(VHost) 隔离不同业务的消息空间(类似数据库的 Schema),提高安全性

队列、交换机、绑定、路由键、虚拟主机等都可以通过可视化管理界面定义

消息模式

  1. 简单模式(一对一)

    场景:单个生产者发送消息给单个消费者

    流程:生产者 → 队列 → 消费者

  2. 工作队列模式(一对多)

    场景:多个消费者共同处理一个队列的消息,实现负载均衡(如秒杀请求分布式处理)

    特点:一条消息只会被一个消费者处理,避免重复消费

  3. 发布 / 订阅模式(广播)

    场景:一条消息需要被多个消费者接收(如订单创建后,通知库存、物流、积分服务)

    原理:使用 Fanout 类型交换机,消息会转发给所有绑定到该交换机的队列,每个消费者监听一个队列

  4. 路由模式(按规则转发)

    场景:消息需要按特定规则分发

    原理:使用 Direct 类型交换机,消息的路由键需与绑定的路由键完全匹配才会转发

    既可以实现消息由交换机只发给某一个队列,也可以实现上面的广播

  5. 主题模式(模糊匹配)

    场景:更灵活的规则匹配

    原理:使用 Topic 类型交换机,支持通配符(* 匹配一个单词,# 匹配多个单词),例如 user.# 匹配 user.loginuser.register

快速使用

  1. 引入依赖

    基于Spring AMQP

    xml 复制代码
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
  2. 配置yml

    yml 复制代码
    spring:
      rabbitmq:
        host: localhost
        port: 5672
        username: guest
        password: guest
        virtual-host: /
  3. 定义交换机、队列及绑定关系

    第一种方法通过配置类定义

    java 复制代码
    @Configuration
    public class RabbitConfig {
        // 声明交换机
        @Bean
        public TopicExchange orderExchange() {
            return ExchangeBuilder.topicExchange("order.exchange").durable(true).build();
        }
    
        // 声明队列
        @Bean
        public Queue stockQueue() {
            return QueueBuilder.durable("stock.queue").build();
        }
    
        // 绑定交换机和队列(路由键:order.stock)
        @Bean
        public Binding stockBinding(TopicExchange orderExchange, Queue stockQueue) {
            return BindingBuilder.bind(stockQueue).to(orderExchange).with("order.stock");
        }
    }

    第二种通过注解定义

    java 复制代码
    // 消费者类中直接声明(发送消息时自动创建交换机/队列)
    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "stock.queue", durable = "true"), // 队列
        exchange = @Exchange(value = "order.exchange", type = ExchangeTypes.TOPIC), // 交换机
        key = "order.stock" // 路由键
    ))
    public void handleStock(Long orderId) {
        // 处理业务
    }
  4. 发送消息

    java 复制代码
    @Service
    public class OrderService {
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        public void createOrder(Long orderId) {
            // 发送消息(交换机、路由键、消息体)
            rabbitTemplate.convertAndSend("order.exchange", "order.stock", orderId);
        }
    }
  5. 消费消息

    java 复制代码
    @Service
    public class StockService {
        @RabbitListener(queues = "stock.queue")
        public void handleStock(Long orderId) {
            // 处理库存扣减逻辑
            System.out.println("扣减订单:" + orderId + " 的库存");
        }
    }

详细讲解

可视化界面

  1. Overview(总览)

    涉及节点,和部署集群相关

  2. Connections(连接)

    在springboot单节点项目中,连接就只有我们项目这一个

    如果是在springcloud分布式项目中,多个模块连接到消息队列中,每个模块都是一个连接

  3. Channels(通道)

  4. Exchanges(交换机)

    重点

  5. Queues(队列)

    重点

  6. Admin(用户管理)

    虚拟主机隔离各个用户,默认虚拟主机为斜杠 /

    最好每个用户对应单独一个虚拟主机

    虚拟主机配置

模型使用

  1. 简单模式

    一对一模式

    发布:

    java 复制代码
    @SpringBootTest
    public class SpringAmqpTest {
        @Resource
        private RabbitTemplate rabbitTemplate;
    
        @Test
        public void testSendMessage() {
            String queueName = "simple.queue";
            String message = "Hello World!";
            rabbitTemplate.convertAndSend(queueName, message);
        }
    }

    监听:

    java 复制代码
    @Component
    public class MqListener {
        @RabbitListener(queues = "simple.queue")
        public void receiveMessage(String message) {
            System.out.println("Received <" + message + ">");
        }
    }

    基本也没什么考虑的东西

    现在发布者代码写在测试类中,需注意测试类的写法(@SpringBootTest + @Resource + @Test)

    实际项目中发布者肯定是对应某个接口,接口接收请求,然后调用发布者方法发布消息到消息队列中

    对应的监听者就自动获取消息并执行

    注意监听者要@Component

    测试时,我们直接运行测试类的方法即可,Spring会自动注入需要的bean

  2. 工作队列模式

    多个监听者监听一个发布者

    此时就涉及到负载均衡的问题

    默认中,不管监听者中的Thread.sleep多久(耗时多久),每个监听者最后处理的消息数量都是一样的

    消息队列将消息均匀轮询发送给所有监听者

    但这并不好,因为处理慢的和处理快的会被分配一样的工作内容,最后快的做完了,慢的还在继续很久,导致消息被堆积

    要提高效率,就应该能者多劳

    只需添加配置到application.yml即可:

    yml 复制代码
    spring:
      rabbitmq:
        host: localhost
        port: 5672
        username: slc
        password: slc
        virtual-host: /slc
        listener:
          simple:
            prefetch: 1	# 每次只能获取一条消息,处理完才获取下一条

    发布者:

    java 复制代码
    @SpringBootTest
    public class SpringAmqpTest {
        @Resource
        private RabbitTemplate rabbitTemplate;
    
        @Test
        public void testSendMessage2() {
            String name = "work.queue";
            String message = "Hello World!";
            for (int i = 0; i < 50; i++){
                rabbitTemplate.convertAndSend(name, message + i);
            }
        }
    }

    监听者:

    java 复制代码
    @Component
    public class WorkListener {
        @RabbitListener(queues = "work.queue")
        public void receiveMessage(String message) throws InterruptedException {
            System.out.println("Received <" + message + ">");
            Thread.sleep(100);
        }
    
        @RabbitListener(queues = "work.queue")
        public void receiveMessage2(String message) throws InterruptedException {
            System.out.println("Received <" + message + ">");
            Thread.sleep(1000);
        }
    }

    但通常,发布者并不直接将消息发送到队列,而是发给交换机,也就是下面三种模式

  3. 发布 / 订阅模式

    Fanout交换机

    广播模式,不筛选,交换机直接将消息发给所有队列

    先在图形化界面中定义多个队列、一个Fanout交换机,并建立二者关系

    • 队列:fanout.queue1、fanout.queue1
    • 交换机:fanout.exchange

    建立监听者监听这个多个队列

    建立发布者向交换机发送消息(不再直接给队列发送消息)

    监听者:

    java 复制代码
    @Component
    public class FanoutListener {
    
        @RabbitListener(queues = "fanout.queue1")
        public void receiveMessage1(String message) {
            System.out.println("fanout.queue1:Received <" + message + ">");
        }
    
        @RabbitListener(queues = "fanout.queue2")
        public void receiveMessage2(String message) {
            System.out.println("fanout.queue2:Received <" + message + ">");
        }
    }

    发布者:

    java 复制代码
    // 广播模式Fanout
    @Test
    public void testSendMessage3() {
        String name = "fanout.exchange";
        String message = "Hello World!";
        rabbitTemplate.convertAndSend(name, "", message);
    }

    广播,两个都收到:

    需要注意的就是,发布者就是向交换机发送消息,第二个参数传空或者null

    下面两种模式,就会传递第二个参数,对应路由键,让发布者将消息通过交换机,只发送给部分匹配的队列中

  4. 路由模式

    Direct交换机

    这种模式就既可以实现上面的广播模式,也可以实现发送给部分队列

    首先需要创建队列、交换机,并在交换机中指定队列的路由规则(添加路由键,这里可多个)

    • 队列:direct.queue1、direct.queue2

    • 交换机:direct.exchange

    • 交换机绑定队列时添加路由键:

    监听者:

    java 复制代码
    @Component
    public class DirectListener {
        @RabbitListener(queues = "direct.queue1")
        public void receiveMessage1(String message) {
            System.out.println("direct.queue1:Received <" + message + ">");
        }
    
        @RabbitListener(queues = "direct.queue2")
        public void receiveMessage2(String message) {
            System.out.println("direct.queue2:Received <" + message + ">");
        }
    }

    发布者:

    java 复制代码
    // 路由模式
    @Test
    public void testSendMessage4() {
        String name = "direct.exchange";
        String message = "Hello World! red";
        String message1 = "Hello World! blue";
        rabbitTemplate.convertAndSend(name, "red", message);
        rabbitTemplate.convertAndSend(name, "blue", message1);
    }

    red两个队列都收到了

    bule只有队列1收到了

  5. 主题模式

    Topic交换机

    更灵活的路由模式

    * 匹配一个单词,# 匹配多个单词

    发布时指定准确的key,绑定时可使用通配符(实现广播效果)

    队列:topic.queue1、topic.queue1

    交换机:topic.exchange

    监听者:

    java 复制代码
    @Component
    public class TopicListener {
        @RabbitListener(queues = "topic.queue1")
        public void receiveMessage1(String message) {
            System.out.println("topic.queue1:Received <" + message + ">");
        }
    
        @RabbitListener(queues = "topic.queue2")
        public void receiveMessage2(String message) {
            System.out.println("topic.queue2:Received <" + message + ">");
        }
    }

    发布者:

    java 复制代码
    // 主题模式
    @Test
    public void testSendMessage5() {
        String name = "topic.exchange";
        String message = "Hello World! china.one";
        String message1 = "Hello World! china.news";
        rabbitTemplate.convertAndSend(name, "china.one", message);
        rabbitTemplate.convertAndSend(name, "china.news", message1);
    }

    china.one只有队列1收到了

    china.news两个都收到了

定义交换机和队列

上面讲解中都是直接通过可视化界面来定义队列、交换机,并实现绑定

但实际使用中肯定是要用代码来实现的,这就对应两种方式:

  1. 通过配置文件

    对应三个类

    • Queue:声明队列
    • Exchange:声明交换机
    • Binding:声明队列和交换机的绑定关系
    java 复制代码
    @Configuration
    public class FanoutConfiguration {
        // 定义广播交换机
        @Bean
        public FanoutExchange fanoutExchange() {
            return new FanoutExchange("fanout.exchange");
        }
    
        // 定义路由交换机
        @Bean
        public DirectExchange directExchange() {
            return new DirectExchange("direct.exchange");
        }
    
        // 定义主题交换机
        @Bean
        public TopicExchange topicExchange() {
            return new TopicExchange("topic.exchange");
        }
    
        // 定义队列
        @Bean
        public Queue fanoutQueue() {
            return new Queue("fanout.queue");
        }
    
        // 绑定队列到广播交换机(通过参数绑定)
        @Bean
        public Binding fanoutBinding(FanoutExchange fanoutExchange, Queue fanoutQueue) {
            return BindingBuilder.bind(fanoutQueue).to(fanoutExchange);
        }
    
        // 绑定队列到路由交换机(通过参数绑定)
        @Bean
        public Binding directBinding(DirectExchange directExchange, Queue fanoutQueue) {
            return BindingBuilder.bind(fanoutQueue).to(directExchange).with("red");
        }
    
        // 定义队列1
        @Bean
        public Queue fanoutQueue1() {
            return new Queue("fanout.queue1");
        }
    
        // 绑定队列1到交换机(通过方法绑定,不是直接调用本类的方法,而是也去spring中找,和上面的写法作用一样)
        @Bean
        public Binding fanoutBinding1() {
            return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange());
        }
    }

    这样会比较复杂,包括定义交换机、队列等,在绑定关系时,如果要

  2. 直接写在监听者上面的注解

    java 复制代码
    // 通过注解方式,快速定义交换机、队列、绑定关系
    @RabbitListener(bindings = @QueueBinding(		// 定义绑定
            value = @Queue(name = "direct.queue1",durable = "true"),	// 定义队列,选择持久化(durable)
            exchange = @Exchange(name = "direct.exchange", type = ExchangeTypes.DIRECT),	// 定义交换机,选择种类
            key =  {"red","blue"}	// 定义路由键
    ))
    public void receiveMessage3(String message) {
        System.out.println("direct.queue1:Received <" + message + ">");
    }

总结:

关于bean:

  • Queue
  • FanoutExchange
  • DirectExchange
  • TopicExchange
  • Binding

关于注解:

@RabbitListener

@QueueBinding

@Queue

@Exchange

消息转换器

我们之前,发布者都是直接向交换机、队列中发送字符串作为消息,但平时更多的肯定是对象类型

如果不使用消息转换器,传递对象时就会乱码

因此就需要用消息转换器

简单来说,当传递的消息是字节类型、字符串类型,则可以直接传输

而当我们传递对象时,会调用jdk自带的ObjectOutputStream将对象转为字节码,导致乱码

我们要避免乱码,就需要自定义消息转换器

例如使用jackson的Jackson2JsonMessageConverter

  1. 导入依赖:(Springboot中自带)
xml 复制代码
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
  1. 在publicsher和consumer中都配置MessageConverter
java 复制代码
@Bean
public MessageConverter jacksonMessageConverter() {
    return new Jackson2JsonMessageConverter();
}

// 配置消息转换器时需要注意MessageConverter要选择amqp的,指定是给amqp定义转换器
//import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
//import org.springframework.amqp.support.converter.MessageConverter;

消息id

消息id由发布者生成,若我们没有自定义消息id,RabbitTemplate会自动创建

可以通过在监听者的方法参数中直接获取到消完整的消息对象,或者消息id

如:

java 复制代码
@Component
public class OrderConsumer {
    
    //直接获取消息id
    @RabbitListener(queues = "queue.order")
    public void receiveOrder(Order order, @Header(AmqpHeaders.MESSAGE_ID) String messageId) {
        System.out.println("消息ID: " + messageId);
        System.out.println("订单内容: " + order);
    }
    
    // 或者获取完整的Message对象,再从Message对象中拿到消息id
    @RabbitListener(queues = "queue.order")
    public void receiveOrder(Message message) {
        String messageId = message.getMessageProperties().getMessageId();
        Order order = (Order) rabbitTemplate.getMessageConverter()
                            .fromMessage(message);
        System.out.println("消息ID: " + messageId);
        System.out.println("订单内容: " + order);
    }
}
相关推荐
2501_941144423 小时前
云计算与大数据:引领数字经济的双引擎
rabbitmq
2501_941144033 小时前
边缘计算与云计算的协作:打造未来智能化网络的新格局
rabbitmq
2501_941403763 小时前
边缘计算与云计算的协同进化:推动智能时代的无限可能
rabbitmq
zl9798998 小时前
RabbitMQ-下载安装与Web页面
linux·分布式·rabbitmq
Q***l68710 小时前
后端服务网格可观测性,Jaeger追踪可观测性实践:Jaeger追踪详解
spring cloud·objective-c·p2p
q***235711 小时前
RabbitMQ介绍以及基本使用
qt·rabbitmq·ruby
没有bug.的程序员12 小时前
JVM 整体架构:一套虚拟机的心脏与血管
java·jvm·spring boot·spring cloud·架构
这人很懒没留下什么14 小时前
SpringBoot2.7.4整合RabbitMq
rabbitmq·springboot
zl97989914 小时前
RabbitMQ-Work Queues
分布式·rabbitmq