RabbitMQ的使用

RabbitMQ的使用

相关知识

  • AMQP协议
    • AMQP是一种开放标准的消息协议,旨在为消息中间件提供统一的通信框架。它的核心特性包括:
      • 消息可靠性:支持消息的持久化、确认机制和事务。
      • 灵活的路由:通过交换器(Exchange)和绑定(Binding)实现消息的灵活路由。
      • 跨平台和语言:AMQP是协议级别的标准,支持多种编程语言和平台。
  • Spring AMQP Core
    • Spring AMQP Core是Spring框架对AMQP协议的抽象和封装,提供了以下核心功能:
      • 抽象接口:定义了与AMQP交互的核心接口,如AmqpTemplateConnectionFactory等。
      • 消息转换:支持将Java对象转换为消息体(如JSON、XML等)。
      • 事务管理:支持声明式事务管理。
      • 监听容器:提供了消息监听容器,用于异步接收消息。
  • Spring RabbitMQ
    • Spring RabbitMQ是基于RabbitMQ的Spring AMQP实现,提供了对RabbitMQ的深度集成。RabbitMQ是一个广泛使用的开源消息代理,完全实现了AMQP协议。核心特性
      • RabbitTemplate:用于发送和接收消息的工具类。
      • 消息监听器:通过@RabbitListener注解实现消息的异步消费。
      • 声明式配置:通过注解或Java配置声明队列、交换器和绑定。
      • 高级特性:支持消息确认、死信队列、延迟队列等。

交换机类型

  • RabbitMQ 中的 交换机(Exchange) 是消息路由的核心组件,它负责接收生产者发送的消息,并根据特定的规则将消息路由到一个或多个队列。
  • 由于队列中的消息只能被一个消费者处理,所以可以通过交换机同时发给多个队列而无需手动分发
  • RabbitMQ 支持多种交换机类型,每种类型有不同的路由行为。
  • 以下是 RabbitMQ 中四种主要的交换机类型及其详细说明:

直连交换机(Direct Exchange)

  • 路由规则:将消息路由到与 路由键(Routing Key) 完全匹配的队列。

  • 使用场景:适用于消息需要精确路由到特定队列的场景。

  • 工作原理

    • 生产者发送消息时,指定一个路由键(如 order.created)。
    • 交换机将消息路由到与路由键完全匹配的绑定队列。
  • 示例

    • // 定义直连交换机
      @Bean
      public DirectExchange directExchange() {
          return new DirectExchange("directExchange");
      }
      
      // 绑定队列到交换机
      @Bean
      public Binding binding(Queue queue, DirectExchange exchange) {
          return BindingBuilder.bind(queue).to(exchange).with("order.created");
      }
      
      // 发送消息
      rabbitTemplate.convertAndSend("directExchange", "order.created", "Order created!");
      

主题交换机(Topic Exchange)

  • 路由规则:将消息路由到与 路由键模式 匹配的队列。

  • 使用场景:适用于消息需要根据模式路由到多个队列的场景。

  • 工作原理

    • 生产者发送消息时,指定一个路由键(如 order.created.us)。
    • 交换机将消息路由到与路由键模式匹配的绑定队列。
    • 路由键模式支持通配符:
      • *:匹配一个单词(如 order.* 可以匹配 order.created,但不能匹配 order.created.us)。
      • #:匹配零个或多个单词(如 order.# 可以匹配 order.createdorder.created.us)。
  • 示例

    • @Bean
      public Queue classicQueue() {
          return new Queue("classicQueue", true); // 第二个参数表示是否持久化
      }
      // 定义主题交换机
      @Bean
      public TopicExchange topicExchange() {
          return new TopicExchange("topicExchange");
      }
      
      // 绑定队列到交换机
      @Bean
      public Binding binding(Queue queue, TopicExchange exchange) {
          return BindingBuilder.bind(queue).to(exchange).with("order.*");
      }
      
      // 发送消息
      rabbitTemplate.convertAndSend("topicExchange", "order.created", "Order created!");
      

扇出交换机(Fanout Exchange)

  • 路由规则:将消息路由到所有绑定的队列,忽略路由键。

  • 使用场景:适用于消息需要广播到多个队列的场景。

  • 工作原理

    • 生产者发送消息时,不需要指定路由键。
    • 交换机会将消息路由到所有绑定的队列。
  • 示例

    • // 定义扇出交换机
      @Bean
      public FanoutExchange fanoutExchange() {
          return new FanoutExchange("fanoutExchange");
      }
      
      // 绑定队列到交换机
      @Bean
      public Binding binding(Queue queue, FanoutExchange exchange) {
          return BindingBuilder.bind(queue).to(exchange);
      }
      
      // 发送消息
      rabbitTemplate.convertAndSend("fanoutExchange", "", "Broadcast message!");
      

头交换机(Headers Exchange)

  • 路由规则:根据消息的 头信息(Headers) 路由消息,忽略路由键。
  • 使用场景:适用于需要根据消息头信息路由消息的场景。
  • 工作原理

    • 生产者发送消息时,指定一组头信息(如 type=order)。
    • 交换机将消息路由到与头信息匹配的绑定队列。
    • 匹配规则:
      • x-match=all:所有头信息都必须匹配。
      • x-match=any:任意一个头信息匹配即可。
  • 示例

    • // 定义头交换机
      @Bean
      public HeadersExchange headersExchange() {
          return new HeadersExchange("headersExchange");
      }
      
      // 绑定队列到交换机
      @Bean
      public Binding binding(Queue queue, HeadersExchange exchange) {
          Map<String, Object> headers = new HashMap<>();
          headers.put("type", "order");
          return BindingBuilder.bind(queue).to(exchange).whereAll(headers).match();
      }
      
      // 发送消息
      MessageProperties properties = new MessageProperties();
      properties.setHeader("type", "order");
      Message message = new Message("Order message!".getBytes(), properties);
      rabbitTemplate.send("headersExchange", "", message);
      

默认交换机(Default Exchange)

  • 路由规则:将消息路由到与 队列名称 完全匹配的队列。

  • 使用场景:RabbitMQ 默认的交换机,适用于简单的消息路由场景。

  • 工作原理

    • 生产者发送消息时,指定队列名称作为路由键。
    • 交换机会将消息路由到与队列名称完全匹配的队列。
  • 示例

    • // 发送消息到默认交换机
      rabbitTemplate.convertAndSend("myQueue", "Hello, RabbitMQ!");
      

总结

交换机类型 路由规则 使用场景
直连交换机 路由键完全匹配 精确路由到特定队列
主题交换机 路由键模式匹配 根据模式路由到多个队列
扇出交换机 忽略路由键,广播到所有绑定队列 广播消息到多个队列
头交换机 根据消息头信息匹配 根据消息头信息路由消息
默认交换机 队列名称完全匹配 简单的消息路由

队列类型

  • 在 RabbitMQ 中,队列(Queue) 是存储消息的缓冲区,消费者从队列中获取消息进行处理。RabbitMQ 提供了多种队列类型,每种类型有不同的特性和适用场景。

经典队列(Classic Queue)

  • 描述:RabbitMQ 的默认队列类型,基于内存和磁盘存储消息。

  • 特点

    • 支持持久化(将消息存储到磁盘,防止消息丢失)。
    • 支持消息确认机制(确保消息被成功处理)。
    • 支持消息 TTL(Time-To-Live,设置消息的存活时间)。
    • 支持死信队列(将无法处理的消息路由到死信队列)。
  • 使用场景:适用于大多数消息队列场景。

  • 配置示例

    • @Bean
      public Queue classicQueue() {
          return new Queue("classicQueue", true); // 第二个参数表示是否持久化
      }
      

惰性队列(Lazy Queue)

  • 描述:一种优化磁盘使用的队列类型,消息尽可能存储在磁盘中,减少内存占用。

  • 特点

    • 消息默认存储在磁盘中,只有在被消费时才会加载到内存。
    • 适合处理大量消息且消息处理速度较慢的场景。
    • 减少内存压力,但可能会增加消息处理的延迟。
  • 使用场景:适用于消息量大、内存有限的场景。

  • 配置示例

    • @Bean
      public Queue lazyQueue() {
          Map<String, Object> args = new HashMap<>();
          args.put("x-queue-mode", "lazy"); // 设置为惰性队列
          return new Queue("lazyQueue", true, false, false, args);
      }
      

优先级队列(Priority Queue)

  • 描述:支持消息优先级的队列类型,优先级高的消息会被优先消费。

  • 特点

    • 可以为消息设置优先级(0-255,数值越大优先级越高)。
    • 高优先级的消息会被优先消费。
    • 需要消费者支持优先级处理。
  • 使用场景:适用于需要优先处理某些消息的场景,如订单系统中的紧急订单。

  • 配置示例

    • @Bean
      public Queue priorityQueue() {
          Map<String, Object> args = new HashMap<>();
          args.put("x-max-priority", 10); // 设置最大优先级为 10
          return new Queue("priorityQueue", true, false, false, args);
      }
      
  • 发送优先级消息

    • MessageProperties properties = new MessageProperties();
      properties.setPriority(5); // 设置消息优先级
      Message message = new Message("High priority message!".getBytes(), properties);
      rabbitTemplate.send("priorityQueue", message);
      

死信队列(Dead Letter Queue, DLQ)

  • 描述:用于存储无法被正常处理的消息的队列。

  • 特点

    • 当消息被拒绝、过期或队列达到最大长度时,消息会被路由到死信队列。
    • 死信队列可以用于分析和处理异常消息。
  • 使用场景:适用于需要处理异常消息的场景。

  • 配置示例

    • @Bean
      public Queue mainQueue() {
          Map<String, Object> args = new HashMap<>();
          args.put("x-dead-letter-exchange", "dlxExchange"); // 设置死信交换机
          args.put("x-dead-letter-routing-key", "dlxRoutingKey"); // 设置死信路由键
          return new Queue("mainQueue", true, false, false, args);
      }
      
      @Bean
      public Queue deadLetterQueue() {
          return new Queue("deadLetterQueue", true);
      }
      
      @Bean
      public DirectExchange dlxExchange() {
          return new DirectExchange("dlxExchange");
      }
      
      @Bean
      public Binding dlxBinding(Queue deadLetterQueue, DirectExchange dlxExchange) {
          return BindingBuilder.bind(deadLetterQueue).to(dlxExchange).with("dlxRoutingKey");
      }
      

延迟队列(Delayed Queue)

  • 描述:支持延迟消息的队列类型,消息在指定的延迟时间后才会被消费。

  • 特点

    • 消息在达到延迟时间后才会进入队列。
    • 需要安装 RabbitMQ 的延迟消息插件(rabbitmq_delayed_message_exchange)。
  • 使用场景:适用于需要延迟处理消息的场景,如订单超时未支付取消。

  • 配置示例

    • @Bean
      public CustomExchange delayedExchange() {
          Map<String, Object> args = new HashMap<>();
          args.put("x-delayed-type", "direct"); // 设置延迟交换机的类型
          return new CustomExchange("delayedExchange", "x-delayed-message", true, false, args);
      }
      // x-delayed-type:设置延迟交换机的类型(如 direct、topic 等)。
      // x-delayed-message:表示这是一个延迟交换机。
      
      @Bean
      public Queue delayedQueue() {
          return new Queue("delayedQueue", true);
      }
      
      @Bean
      public Binding delayedBinding(Queue delayedQueue, CustomExchange delayedExchange) {
          return BindingBuilder.bind(delayedQueue).to(delayedExchange).with("delayedRoutingKey").noargs();
      }
      
    • 使用ExchangeBuilder 创建延迟交换机

      • @Configuration
        public class RabbitConfig {
        
            @Bean
            public DirectExchange delayedExchange() {
                return ExchangeBuilder
                        .directExchange("delayedExchange")  // 交换机名称
                        .durable(true)                       // 持久化
                        .delayed()                           // 标记为延迟交换机(自动添加 x-delayed-type)
                        .build();
            }
        }
        
    • 使用@RabbitListener创建延迟交换机

      • @RabbitListener(
            bindings = @QueueBinding(
                value = @Queue(name = "delay",
                               durable = "true"                         
                              ),
                exchange = @Exchange(name = "exchange",
                                     type = "direct",
                                     delayed="true"),//标记为延迟交换机
                key = "delay"
            ))
        public void listen1(String message){
            System.out.println("延迟交换机");
        }
        
  • 发送延迟消息

    • MessageProperties properties = new MessageProperties();
      properties.setHeader("x-delay", 5000); // 设置延迟时间为 5 秒
      Message message = new Message("Delayed message!".getBytes(), properties);
      rabbitTemplate.send("delayedExchange", "delayedRoutingKey", message);
      
  • 延迟消息的存储

    • 延迟消息保存在交换机中,而不是队列中。
    • 当消息发送到延迟交换机时,交换机会根据消息的 x-delay 头信息计算出消息的投递时间,并将消息存储在内部的一个延迟队列中。
    • 当消息的延迟时间到达后,交换机会将消息路由到目标队列,供消费者消费。
  • 消息 TTL 和死信队列 是一种替代方案,但实现较为复杂。

使用RabbitMQ

启动RabbitMQ

添加依赖

  • pom.xml中添加Spring AMQP和RabbitMQ的依赖:

  • <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    
    • spring-boot-starter-amqp 已经包含了 RabbitMQ 的依赖(如 amqp-clientspring-rabbit

配置连接信息

  • application.properties中配置RabbitMQ连接信息:

  • # RabbitMQ 服务器地址
    spring.rabbitmq.host=localhost
    
    # RabbitMQ 服务器端口(默认是 5672)
    spring.rabbitmq.port=5672
    
    # RabbitMQ 用户名(默认是 guest)
    spring.rabbitmq.username=guest
    
    # RabbitMQ 密码(默认是 guest)
    spring.rabbitmq.password=guest
    
    # 虚拟主机(默认是 /)
    spring.rabbitmq.virtual-host=/
    
    # 连接超时时间(单位:毫秒)
    spring.rabbitmq.connection-timeout=5000
    
    # 是否启用 SSL(默认是 false)
    spring.rabbitmq.ssl.enabled=false
    

定义队列、交换机和绑定

队列

  • 在 Spring AMQP 中,Queue 的构造函数有多个参数,用于定义队列的属性和行为。

  • public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
    
    • name
      • 类型String
      • 含义:队列的名称。
    • durable
      • 类型boolean
      • 含义:队列是否持久化。
        • true:队列是持久化的,即使 RabbitMQ 服务器重启,队列仍然存在。
        • false:队列是非持久化的,RabbitMQ 服务器重启后,队列会被删除。
    • exclusive
      • 类型boolean
      • 含义:队列是否是独占的。
        • true:队列是独占的,只能被当前连接使用。当连接关闭时,队列会被自动删除。
        • false:队列是非独占的,可以被多个连接使用。
    • autoDelete
      • 类型boolean
      • 含义:队列是否自动删除。
        • true:当最后一个消费者断开连接时,队列会被自动删除。
        • false:队列不会自动删除,即使没有消费者。
    • arguments
      • 类型Map<String, Object>
      • 含义:队列的附加参数,用于设置队列的高级特性。
        • 例如:设置队列为惰性队列(x-queue-mode=lazy)、设置消息 TTL(x-message-ttl)等。
  • 队列的创建方式

    • 在 Spring 配置类中,使用 @Bean 注解定义队列。

    • 在 Spring AMQP 中,Queue 类是用于定义 RabbitMQ 队列的核心类。它位于 org.springframework.amqp.core 包下。

    • import org.springframework.amqp.core.Queue;
      @Configuration
      public class RabbitMQConfig {
      
          @Bean
          public Queue myQueue() {
              return new Queue("myQueue", true, false, false);
          }
      }
      
  • 队列的高级配置

    • 通过 arguments 参数,可以为队列设置高级特性。
参数名 类型 说明
x-message-ttl Integer 消息的存活时间(Time-To-Live),单位为毫秒。
x-max-length Integer 队列的最大消息数量,超过该数量时,旧消息会被丢弃。
x-max-length-bytes Integer 队列的最大消息总大小(字节),超过该大小时,旧消息会被丢弃。
x-dead-letter-exchange String 死信交换机名称,用于将无法处理的消息路由到死信队列。
x-dead-letter-routing-key String 死信路由键,用于指定死信消息的路由键。
x-max-priority Integer 队列支持的最大优先级(0-255),用于优先级队列。
x-queue-mode String 队列模式,可选值为 default(默认)或 lazy(惰性队列)。
  • @Configuration
    public class RabbitMQConfig {
    
        @Bean
        public Queue myQueue() {
            Map<String, Object> args = new HashMap<>();
            args.put("x-message-ttl", 60000);  // 消息存活时间为 60 秒
            args.put("x-max-length", 1000);    // 队列最大消息数量为 1000
            args.put("x-dead-letter-exchange", "dlxExchange");  // 设置死信交换机
            args.put("x-dead-letter-routing-key", "dlxRoutingKey");  // 设置死信路由键
            return new Queue("myQueue", true, false, false, args);
        }
    }
    
  • QueueBuilder 创建队列

    • Queue customQueue = QueueBuilder.durable("customQueue")
          .autoDelete()  // 当所有消费者断开连接后自动删除
          .exclusive()   // 排他队列,仅限此连接使用
          .withArgument("x-message-ttl", 60000)  // 消息存活时间60秒
          .withArgument("x-max-length", 1000)    // 队列最大消息数
          .withArgument("x-max-length-bytes", 10485760) // 队列最大容量(10MB)
          .withArgument("x-dead-letter-exchange", "dlx.exchange") // 死信交换机
          .withArgument("x-dead-letter-routing-key", "dlx.routingKey") // 死信路由键
          .build();
      

交换机

  • 交换机的构造函数参数如下

    • public DirectExchange(String name, boolean durable, boolean autoDelete, Map<String, Object> arguments)
      
    • 其中和队列对应的参数含义相同

  • 在 Spring 配置类中,使用 @Bean 注解定义交换机。

    • @Configuration
      public class RabbitMQConfig {
      
          // 创建直连交换机
          @Bean
          public DirectExchange directExchange() {
              return new DirectExchange("directExchange");
          }
      
          // 创建主题交换机
          @Bean
          public TopicExchange topicExchange() {
              return new TopicExchange("topicExchange");
          }
      
          // 创建扇出交换机
          @Bean
          public FanoutExchange fanoutExchange() {
              return new FanoutExchange("fanoutExchange");
          }
      
          // 创建头交换机
          @Bean
          public HeadersExchange headersExchange() {
              return new HeadersExchange("headersExchange");
          }
      }
      
    • map中的常用参数

    参数名 类型 说明
    x-delayed-type String 延迟交换机的类型(如 directtopic 等)。
    alternate-exchange String 备用交换机名称,用于处理无法路由的消息。
  • ExchangeBuilder 创建交换机

    • Exchange customExchange = ExchangeBuilder.topicExchange("advanced.exchange")
          .durable(true)    // 持久化交换机
          .autoDelete()     // 当所有队列都断开连接后自动删除
          .internal()       // 内部交换机,不能直接发布消息
          .withArgument("alternate-exchange", "ae.exchange") // 备用交换机
          .delayed()        // 支持延迟消息(需要插件)
          .build();
      

绑定

  • 创建完成队列和交换机后则需要将队列和交换机进行绑定

  • 队列之间不能同名:在同一个虚拟主机中,队列的名称必须是唯一的。

  • 交换机之间不能同名:在同一个虚拟主机中,交换机的名称也必须是唯一的。

  • 在 Spring 配置类中,使用 @Bean 注解定义绑定。

    • //直连交换机绑定
      @Bean
      public Binding directBinding(Queue myQueue, DirectExchange directExchange) {
          return BindingBuilder.bind(myQueue)
                  .to(directExchange)
                  .with("routingKey");
      }
      
      //主题交换机绑定
      @Bean
      public Binding topicBinding(Queue myQueue, TopicExchange topicExchange) {
          return BindingBuilder.bind(myQueue)
                  .to(topicExchange)
                  .with("routing.*");
      }
      
      //扇出交换机绑定
      @Bean
      public Binding fanoutBinding(Queue myQueue, FanoutExchange fanoutExchange) {
          return BindingBuilder.bind(myQueue)
                  .to(fanoutExchange);
      }
      
      //头交换机绑定
      @Bean
      public Binding headersBinding(Queue myQueue, HeadersExchange headersExchange) {
          Map<String, Object> args = new HashMap<>();
          args.put("key1", "value1"); // 头信息键值对
          args.put("x-match", "all");	// 匹配规则
          return BindingBuilder.bind(myQueue)
                  .to(headersExchange)
                  .whereAll(args)
                  .match();
      }
      

注解方式进行绑定

  • @RabbitListener 注解不仅可以用于监听队列,还可以通过 bindings 属性直接定义队列、交换机以及绑定关系。

  • 注解属性

    • queues:指定监听的队列名称。
    • bindings:用于定义队列、交换机以及绑定关系。
  • @RabbitListenerbindings 属性中,可以使用 @QueueBinding 注解来定义绑定关系。

  • @QueueBinding 注解的属性

    • value:指定队列(@Queue 注解)。
    • exchange:指定交换机(@Exchange 注解)。
    • key:指定路由键。
  • @Configuration
    @EnableRabbit  // 启用 RabbitMQ 注解支持
    public class RabbitMQConfig {
    
        // 使用 @RabbitListener 注解绑定队列和交换机
        @RabbitListener(
                bindings = @QueueBinding(
                        value = @Queue(value = "myQueue", durable = "true"),  // 定义队列
                        exchange = @Exchange(value = "myExchange", type = ExchangeTypes.DIRECT),  // 定义交换机
                        key = "myRoutingKey"  // 定义路由键
                )
        )
        public void receiveMessage(String message) {
            System.out.println("Received message: " + message);
        }
    }
    
  • 适用于需要快速定义队列、交换机和绑定关系的场景。

  • 如果 RabbitMQ 中已经存在@Exchange 注解中指定的名称(value)相同的交换机,RabbitMQ 会直接绑定到该交换机,而不会重新创建。

    • 触发条件
      • RabbitMQ 中已经存在名为 annoExchange 的交换机。
      • 已有交换机的属性(如类型、持久化等)与 @Exchange 注解中的配置完全一致

发送消息

  • 使用 RabbitTemplate 发送消息到队列。

    • @Service
      public class MessageSender {
      
          @Autowired
          private AmqpTemplate rabbitTemplate;   
      }
      
  • 发送消息到指定的交换器和路由键

    •     /**
           * 发送消息到指定的交换器和路由键
           *
           * @param exchange   交换器名称
           * @param routingKey 路由键
           * @param message    消息内容
           */
      	public void sendMessage(String exchange, String routingKey, String message) {
      		rabbitTemplate.convertAndSend(exchange, routingKey, message);
      		System.out.println("Sent message: " + message);
      	}
      
  • 直接发送到队列

    •     /**
           * 发送消息到默认的交换器(直接发送到队列)
           *
           * @param queueName 队列名称
           * @param message   消息内容
           */
          public void sendMessageToQueue(String queueName, String message) {
              rabbitTemplate.convertAndSend(queueName, message);
              System.out.println("Sent message to queue: " + message);
          }
      
  • 在需要发送消息的地方调用 MessageSender 的方法即可。

  • 发送消息的内容不能为空

    • rabbitTemplate.convertAndSend("exchange","lazy","");
      
    • 上述代码会报错

    • Spring AMQP 默认使用 SimpleMessageConverter,它可能无法正确处理空消息。

    • 如果消息体为空,消费者在尝试将空消息转换为目标类型时可能会抛出异常。

    • 也就是说可以正常发送,无法正常接收

  • Message

    • 在 RabbitMQ 中,Message 是 Spring AMQP 提供的一个类,用于封装消息的内容和属性。Message 对象包含两部分:

      • 消息体(Body):消息的实际内容,通常是字节数组(byte[])。
      • 消息属性(Properties):消息的元数据,如消息头、优先级、过期时间等。类型为:MessageProperties
    • Message 类的主要作用包括:

      • 封装消息内容
        • 将消息的实际数据(如字符串、JSON、二进制数据)封装为字节数组。
      • 存储消息属性
        • 通过 MessageProperties 存储消息的元数据,如消息头、内容类型、优先级等。
      • 与 RabbitMQ 交互
        • RabbitTemplate 使用 Message 对象发送消息。
        • @RabbitListener 使用 Message 对象接收消息。
    • 发送指定格式内容的消息

      • @Service
        public class MessageSender {
        
            @Autowired
            private RabbitTemplate rabbitTemplate;
        
            public void sendMessage(String exchange, String routingKey, String content) {
                // 创建 MessageProperties
                MessageProperties properties = new MessageProperties();
                properties.setContentType("text/plain"); // 设置内容类型为纯文本
        
                // 创建 Message 对象
                Message message = new Message(content.getBytes(), properties);
        
                // 发送消息
                rabbitTemplate.send(exchange, routingKey, message);
            }
        }
        
    • 使用 Message 类接收消息

      • 可以得到设置的各种信息

      • @Component
        public class MessageReceiver {
        
            @RabbitListener(queues = "myQueue")
            public void receiveMessage(Message message) {
                // 获取消息体
                String body = new String(message.getBody());
                System.out.println("Received message body: " + body);
        
                // 获取消息属性
                MessageProperties properties = message.getMessageProperties();
                System.out.println("Content type: " + properties.getContentType());
                System.out.println("Headers: " + properties.getHeaders());
            }
        }
        
  • 通过MessageProperties可以设置的属性

属性名 类型 描述
contentType String 消息的内容类型(如 application/jsontext/plain)。
contentEncoding String 消息内容的编码方式(如 UTF-8)。
headers Map<String, Object> 自定义消息头,用于传递额外的元数据。
deliveryMode Integer 消息的持久化模式:1(非持久化)或 2(持久化)。
priority Integer 消息的优先级(0-9),数值越大优先级越高。
correlationId String 用于关联消息的 ID,通常用于 RPC 模式。
replyTo String 用于指定回复消息的目标队列,通常用于 RPC 模式。
expiration String 消息的过期时间(以毫秒为单位),超过此时间未被消费的消息会被丢弃。
当消息和队列都设置了过期时间时,RabbitMQ 会根据更早过期的时间来决定消息的过期行为。
messageId String 消息的唯一标识符。
timestamp Date 消息的时间戳。
type String 消息的类型,通常用于区分不同的消息类别。
userId String 消息的生产者用户 ID。
appId String 生产者的应用标识符。
clusterId String 集群 ID,用于标识消息的来源集群。

接收消息

  • 使用 @RabbitListener 注解接收消息

  • @RabbitListener 是 Spring AMQP 提供的一个注解,用于声明一个方法作为消息监听器。

  • 该类需要交由Spring容器进行管理

    • @Component
      public class MessageReceiver {
      
          // 监听指定的队列
          @RabbitListener(queues = "myQueue")
          public void receiveMessage(String message) {
              System.out.println("Received message: " + message);
          }
      }
      
    • @RabbitListener 注解标记的方法会自动监听指定的队列(如 myQueue)。

    • 当队列中有消息时,Spring 会调用该方法,并将消息体作为参数传入。

  • 可以一个队列上绑定多个消费者,加快消息处理的速度,MQ默认将队列中的消息按照预取数量分发给各个消费者进行处理

  • 预取数量(Prefetch Count)

    • 预取数量的主要作用是 平衡消息处理的效率和消费者的负载

    • 在 RabbitMQ 中,预取数量的默认值是 0,表示没有限制。这意味着:

      • RabbitMQ 会尽可能多地将消息推送给消费者,直到消费者无法处理更多消息为止。
    • 工作原理

      • 当消费者连接到队列时,RabbitMQ 会根据预取数量一次性推送多条消息给消费者。
      • 消费者在处理完这些消息后,需要向 RabbitMQ 发送确认(ACK),RabbitMQ 才会推送新的消息。
      • 如果消费者在处理消息时崩溃或断开连接,未确认的消息会被重新入队,供其他消费者处理。
    • 预取数量的配置

      • application.propertiesapplication.yml 中配置:

      • spring.rabbitmq.listener.simple.prefetch=10  # 每个消费者最多预取 10 条消息
        

发送和接收复杂对象

  • 在 Spring 框架中使用 RabbitTemplate 发送复杂对象(如 User)时,如果配置 JSON 消息转换器(如 Jackson2JsonMessageConverter)作为RabbitMQ 的消息转换器的话,Spring 会自动完成以下工作:

    • 发送时
      • 将 Java 对象(如 User)序列化为 JSON 格式。
      • 将 JSON 数据作为消息体发送到 RabbitMQ。
    • 接收时
      • 从 RabbitMQ 中获取消息体(JSON 格式)。
      • 将 JSON 数据反序列化为 Java 对象(如 User)。
  • spring-boot-starter-web起步依赖中配置了这个JSON消息转换器

    • 然而,Spring Boot 的自动配置并不会自动将 Jackson2JsonMessageConverter 配置为 RabbitMQ 的消息转换器

    • Spring Boot 不会自动将 Jackson 的 JSON 转换器应用到 RabbitMQ 中,因为 RabbitMQ 的消息格式不仅限于 JSON(还支持二进制、文本等)。

    • 当发送对象时,如果对象类没有实现可序列化接口则会报错

      • java.lang.IllegalArgumentException: SimpleMessageConverter only supports String, byte[] and Serializable payloads, received: Spring.Pojo.User
        
  • 在 Spring MVC 中,@ResponseBody 注解用于将方法的返回值直接写入 HTTP 响应体中。Spring MVC 默认使用 Jackson 作为 JSON 序列化库,因此它会自动将对象转换为 JSON 格式。

    • 为什么不需要实现 Serializable
    • Jackson 的职责:Jackson 是一个独立的 JSON 序列化库,它通过反射直接访问对象的字段和方法,不需要对象实现 Serializable 接口。
    • HTTP 协议的特性:HTTP 响应体通常是文本格式(如 JSON、XML),Spring MVC 默认使用 Jackson 将对象序列化为 JSON 字符串,而不是二进制格式。
  • 在 RabbitMQ 中,消息的传输是基于 AMQP 协议的,消息体可以是任意格式(如二进制、文本、JSON 等)。Spring AMQP 默认使用 SimpleMessageConverter 来处理消息。

    • 为什么需要实现 Serializable
    • SimpleMessageConverter 的职责SimpleMessageConverter 是 Spring AMQP 的默认消息转换器,它支持以下类型的消息体:
      • String(字符串)
      • byte[](字节数组)
      • 实现了 Serializable 接口的对象
    • 二进制传输:RabbitMQ 的消息体通常是二进制格式,SimpleMessageConverter 使用 Java 的序列化机制将对象转换为字节数组。因此,对象必须实现 Serializable 接口。
  • 使用 Jackson2JsonMessageConverter 替代 SimpleMessageConverter 后,二进制数据格式的消息可能会出现问题。这是因为 Jackson2JsonMessageConverter 是专门用于 JSON 序列化和反序列化的,它默认会将消息体当作 JSON 处理。如果尝试发送或接收二进制数据(如 byte[]),Jackson2JsonMessageConverter 可能会无法正确处理。

消息转换器

  • 消息转换器(MessageConverter) 是一个核心组件,它负责将 Java 对象转换为消息体(如 JSON、二进制等),以及将消息体转换回 Java 对象。Spring AMQP 提供了多种内置的消息转换器,同时也支持自定义转换器。

  • 消息转换器的作用

    • 消息转换器的主要功能是:
      • 发送消息时
        • 将 Java 对象转换为消息体(如 JSON、二进制等)。
        • 设置消息的属性(如 contentType)。
      • 接收消息时
        • 将消息体转换回 Java 对象。
        • 根据消息的属性(如 contentType)选择合适的转换逻辑。
  • 内置消息转换器

    • Spring AMQP 提供了以下几种内置的消息转换器:
      • SimpleMessageConverter
        • 默认转换器:如果没有显式配置其他转换器,Spring AMQP 会使用 SimpleMessageConverter
        • 支持的类型
          • String(字符串)
          • byte[](字节数组)
          • 实现了 Serializable 接口的对象
        • 特点
          • 对于 Stringbyte[],直接将其作为消息体。
          • 对于实现了 Serializable 接口的对象,使用 Java 序列化机制将其转换为字节数组。
        • 局限性
          • 不支持 JSON 格式。
          • 要求对象实现 Serializable 接口。
      • Jackson2JsonMessageConverter
        • JSON 转换器:将 Java 对象序列化为 JSON 格式,或将 JSON 格式的消息反序列化为 Java 对象。
        • 支持的类型
          • 任意 Java 对象(不需要实现 Serializable 接口)。
        • 特点
          • 使用 Jackson 库进行 JSON 序列化和反序列化。
          • 支持复杂的对象结构(如嵌套对象、集合等)。
        • 局限性
          • 仅支持 JSON 格式,无法处理二进制数据。
      • ContentTypeDelegatingMessageConverter
        • 组合转换器:根据消息的 contentType 动态选择不同的转换器。
        • 特点
          • 可以同时支持多种消息格式(如 JSON、二进制、文本等)。
          • 需要显式配置支持的转换器。
  • 配置消息转换器

    • 在 Spring Boot 中,可以通过配置类定义消息转换器。

    • 当手动配置一个 MessageConverter Bean 时,它会覆盖默认的 SimpleMessageConverter

      • 配置 Jackson2JsonMessageConverter

      • 导入jackson依赖

      •     <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
            </dependency>
        
        • 然后配置JSON转换器

        • @Configuration
          public class RabbitMQConfig {
          
              @Bean
              public MessageConverter jsonMessageConverter() {
                  return new Jackson2JsonMessageConverter();
              }
          }
          
      • 配置 ContentTypeDelegatingMessageConverter

        • @Configuration
          public class RabbitMQConfig {
          
              @Bean
              public MessageConverter compositeMessageConverter() {
                  ContentTypeDelegatingMessageConverter converter = new ContentTypeDelegatingMessageConverter();
          
                  // 注册 JSON 转换器
                  converter.addDelegate("application/json", new Jackson2JsonMessageConverter());
          
                  // 注册二进制转换器
                  converter.addDelegate("application/octet-stream", new SimpleMessageConverter());
          
                  return converter;
              }
          }
          
  • 发送消息

    • 配置好消息转换器后,可以使用 RabbitTemplate 指定发送消息的类型。

    • @Service
      public class MessageSender {
      
          @Autowired
          private RabbitTemplate rabbitTemplate;
      
          public void sendBinaryMessage(String queueName, byte[] data) {
              rabbitTemplate.convertAndSend(queueName, data, message -> {
                  MessageProperties properties = message.getMessageProperties();
                  properties.setContentType("application/octet-stream"); // 指定内容类型为二进制
                  return message;
              });
              System.out.println("Sent binary message: " + new String(data));
          }
      }
      
  • 接收消息

    • 在接收消息时,Spring 会根据消息的 contentType 自动选择合适的转换器。

    • @Component
      public class MessageReceiver {
      
          @RabbitListener(queues = "myQueue")
          public void handleMessage(Object payload) {
              if (payload instanceof byte[]) {
                  System.out.println("Received binary message: " + new String((byte[]) payload));
              } else {
                  System.out.println("Received JSON message: " + payload);
              }
          }
      }
      
posted @ 2025-03-26 18:29  QAQ001  阅读(45)  评论(0)    收藏  举报