SpringBoot3.x入门到精通系列:3.2 整合 RabbitMQ 详解

🎯 RabbitMQ简介

RabbitMQ是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据。它是使用Erlang语言编写的,并且基于AMQP协议。

核心概念

  • Producer: 消息生产者,发送消息的应用
  • Consumer: 消息消费者,接收消息的应用
  • Queue: 消息队列,存储消息的缓冲区
  • Exchange: 交换机,负责接收消息并路由到队列
  • Routing Key: 路由键,Exchange根据它来决定消息路由到哪个队列
  • Binding: 绑定,Exchange和Queue之间的连接关系

交换机类型

  • Direct: 直连交换机,完全匹配路由键
  • Topic: 主题交换机,支持通配符匹配
  • Fanout: 扇出交换机,广播到所有绑定的队列
  • Headers: 头交换机,根据消息头属性路由

🚀 快速开始

1. 添加依赖

xml 复制代码
<dependencies>
    <!-- SpringBoot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- SpringBoot RabbitMQ Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    
    <!-- JSON处理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
    
    <!-- 测试依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2. RabbitMQ配置

yaml 复制代码
spring:
  # RabbitMQ配置
  rabbitmq:
    # RabbitMQ服务器地址
    host: localhost
    # RabbitMQ服务器端口
    port: 5672
    # 用户名
    username: guest
    # 密码
    password: guest
    # 虚拟主机
    virtual-host: /
    
    # 连接配置
    connection-timeout: 15000
    
    # 生产者配置
    publisher-confirm-type: correlated # 确认模式
    publisher-returns: true # 开启return机制
    
    # 消费者配置
    listener:
      simple:
        # 手动确认模式
        acknowledge-mode: manual
        # 并发消费者数量
        concurrency: 1
        # 最大并发消费者数量
        max-concurrency: 10
        # 每次从队列获取的消息数量
        prefetch: 1
        # 重试机制
        retry:
          enabled: true
          initial-interval: 1000
          max-attempts: 3
          max-interval: 10000
          multiplier: 1.0

# 日志配置
logging:
  level:
    org.springframework.amqp: DEBUG

🔧 RabbitMQ配置类

1. 基础配置

java 复制代码
package com.example.demo.config;

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {
    
    // 队列名称常量
    public static final String DIRECT_QUEUE = "direct.queue";
    public static final String TOPIC_QUEUE_1 = "topic.queue.1";
    public static final String TOPIC_QUEUE_2 = "topic.queue.2";
    public static final String FANOUT_QUEUE_1 = "fanout.queue.1";
    public static final String FANOUT_QUEUE_2 = "fanout.queue.2";
    public static final String DELAY_QUEUE = "delay.queue";
    public static final String DLX_QUEUE = "dlx.queue";
    
    // 交换机名称常量
    public static final String DIRECT_EXCHANGE = "direct.exchange";
    public static final String TOPIC_EXCHANGE = "topic.exchange";
    public static final String FANOUT_EXCHANGE = "fanout.exchange";
    public static final String DELAY_EXCHANGE = "delay.exchange";
    public static final String DLX_EXCHANGE = "dlx.exchange";
    
    /**
     * 消息转换器 - 使用JSON格式
     */
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
    
    /**
     * RabbitTemplate配置
     */
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(messageConverter());
        
        // 设置确认回调
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack) {
                System.out.println("消息发送成功: " + correlationData);
            } else {
                System.out.println("消息发送失败: " + cause);
            }
        });
        
        // 设置返回回调
        rabbitTemplate.setReturnsCallback(returned -> {
            System.out.println("消息返回: " + returned.getMessage());
            System.out.println("回复码: " + returned.getReplyCode());
            System.out.println("回复文本: " + returned.getReplyText());
            System.out.println("交换机: " + returned.getExchange());
            System.out.println("路由键: " + returned.getRoutingKey());
        });
        
        return rabbitTemplate;
    }
    
    /**
     * 监听器容器工厂配置
     */
    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
            ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(messageConverter());
        return factory;
    }
    
    // ========================= Direct Exchange =========================
    
    @Bean
    public Queue directQueue() {
        return QueueBuilder.durable(DIRECT_QUEUE).build();
    }
    
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(DIRECT_EXCHANGE);
    }
    
    @Bean
    public Binding directBinding() {
        return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct.routing.key");
    }
    
    // ========================= Topic Exchange =========================
    
    @Bean
    public Queue topicQueue1() {
        return QueueBuilder.durable(TOPIC_QUEUE_1).build();
    }
    
    @Bean
    public Queue topicQueue2() {
        return QueueBuilder.durable(TOPIC_QUEUE_2).build();
    }
    
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(TOPIC_EXCHANGE);
    }
    
    @Bean
    public Binding topicBinding1() {
        return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("topic.*.message");
    }
    
    @Bean
    public Binding topicBinding2() {
        return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("topic.#");
    }
    
    // ========================= Fanout Exchange =========================
    
    @Bean
    public Queue fanoutQueue1() {
        return QueueBuilder.durable(FANOUT_QUEUE_1).build();
    }
    
    @Bean
    public Queue fanoutQueue2() {
        return QueueBuilder.durable(FANOUT_QUEUE_2).build();
    }
    
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(FANOUT_EXCHANGE);
    }
    
    @Bean
    public Binding fanoutBinding1() {
        return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange());
    }
    
    @Bean
    public Binding fanoutBinding2() {
        return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange());
    }
    
    // ========================= 延迟队列 =========================
    
    @Bean
    public Queue delayQueue() {
        return QueueBuilder.durable(DELAY_QUEUE)
                .withArgument("x-message-ttl", 60000) // 消息TTL 60秒
                .withArgument("x-dead-letter-exchange", DLX_EXCHANGE) // 死信交换机
                .withArgument("x-dead-letter-routing-key", "dlx.routing.key") // 死信路由键
                .build();
    }
    
    @Bean
    public DirectExchange delayExchange() {
        return new DirectExchange(DELAY_EXCHANGE);
    }
    
    @Bean
    public Binding delayBinding() {
        return BindingBuilder.bind(delayQueue()).to(delayExchange()).with("delay.routing.key");
    }
    
    // ========================= 死信队列 =========================
    
    @Bean
    public Queue dlxQueue() {
        return QueueBuilder.durable(DLX_QUEUE).build();
    }
    
    @Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange(DLX_EXCHANGE);
    }
    
    @Bean
    public Binding dlxBinding() {
        return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("dlx.routing.key");
    }
}

📊 消息实体类

java 复制代码
package com.example.demo.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serializable;
import java.time.LocalDateTime;

public class MessageDto implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String id;
    private String content;
    private String type;
    private String sender;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime timestamp;
    
    // 构造函数
    public MessageDto() {
        this.timestamp = LocalDateTime.now();
    }
    
    public MessageDto(String id, String content, String type, String sender) {
        this();
        this.id = id;
        this.content = content;
        this.type = type;
        this.sender = sender;
    }
    
    // Getter和Setter方法
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    
    public String getContent() { return content; }
    public void setContent(String content) { this.content = content; }
    
    public String getType() { return type; }
    public void setType(String type) { this.type = type; }
    
    public String getSender() { return sender; }
    public void setSender(String sender) { this.sender = sender; }
    
    public LocalDateTime getTimestamp() { return timestamp; }
    public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
    
    @Override
    public String toString() {
        return "MessageDto{" +
                "id='" + id + '\'' +
                ", content='" + content + '\'' +
                ", type='" + type + '\'' +
                ", sender='" + sender + '\'' +
                ", timestamp=" + timestamp +
                '}';
    }
}

📤 消息生产者

java 复制代码
package com.example.demo.service;

import com.example.demo.config.RabbitConfig;
import com.example.demo.dto.MessageDto;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.UUID;

@Service
public class MessageProducer {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 发送Direct消息
     */
    public void sendDirectMessage(String content) {
        MessageDto message = new MessageDto(
            UUID.randomUUID().toString(),
            content,
            "DIRECT",
            "Producer"
        );
        
        rabbitTemplate.convertAndSend(
            RabbitConfig.DIRECT_EXCHANGE,
            "direct.routing.key",
            message
        );
        
        System.out.println("发送Direct消息: " + message);
    }
    
    /**
     * 发送Topic消息
     */
    public void sendTopicMessage(String routingKey, String content) {
        MessageDto message = new MessageDto(
            UUID.randomUUID().toString(),
            content,
            "TOPIC",
            "Producer"
        );
        
        rabbitTemplate.convertAndSend(
            RabbitConfig.TOPIC_EXCHANGE,
            routingKey,
            message
        );
        
        System.out.println("发送Topic消息 [" + routingKey + "]: " + message);
    }
    
    /**
     * 发送Fanout消息
     */
    public void sendFanoutMessage(String content) {
        MessageDto message = new MessageDto(
            UUID.randomUUID().toString(),
            content,
            "FANOUT",
            "Producer"
        );
        
        rabbitTemplate.convertAndSend(
            RabbitConfig.FANOUT_EXCHANGE,
            "", // Fanout交换机忽略路由键
            message
        );
        
        System.out.println("发送Fanout消息: " + message);
    }
    
    /**
     * 发送延迟消息
     */
    public void sendDelayMessage(String content, int delaySeconds) {
        MessageDto message = new MessageDto(
            UUID.randomUUID().toString(),
            content,
            "DELAY",
            "Producer"
        );
        
        // 设置消息属性
        rabbitTemplate.convertAndSend(
            RabbitConfig.DELAY_EXCHANGE,
            "delay.routing.key",
            message,
            msg -> {
                // 设置消息过期时间
                msg.getMessageProperties().setExpiration(String.valueOf(delaySeconds * 1000));
                return msg;
            }
        );
        
        System.out.println("发送延迟消息 [" + delaySeconds + "s]: " + message);
    }
    
    /**
     * 发送带优先级的消息
     */
    public void sendPriorityMessage(String content, int priority) {
        MessageDto message = new MessageDto(
            UUID.randomUUID().toString(),
            content,
            "PRIORITY",
            "Producer"
        );
        
        rabbitTemplate.convertAndSend(
            RabbitConfig.DIRECT_EXCHANGE,
            "direct.routing.key",
            message,
            msg -> {
                msg.getMessageProperties().setPriority(priority);
                return msg;
            }
        );
        
        System.out.println("发送优先级消息 [" + priority + "]: " + message);
    }
}

📥 消息消费者

java 复制代码
package com.example.demo.service;

import com.example.demo.config.RabbitConfig;
import com.example.demo.dto.MessageDto;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import java.io.IOException;

@Service
public class MessageConsumer {
    
    /**
     * 消费Direct消息
     */
    @RabbitListener(queues = RabbitConfig.DIRECT_QUEUE)
    public void consumeDirectMessage(MessageDto message, Message msg, Channel channel) throws IOException {
        try {
            System.out.println("接收到Direct消息: " + message);
            
            // 模拟业务处理
            Thread.sleep(1000);
            
            // 手动确认消息
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
            System.out.println("Direct消息处理完成");
            
        } catch (Exception e) {
            System.err.println("处理Direct消息失败: " + e.getMessage());
            // 拒绝消息并重新入队
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
    
    /**
     * 消费Topic消息 - 队列1
     */
    @RabbitListener(queues = RabbitConfig.TOPIC_QUEUE_1)
    public void consumeTopicMessage1(MessageDto message, Message msg, Channel channel) throws IOException {
        try {
            System.out.println("队列1接收到Topic消息: " + message);
            
            // 模拟业务处理
            Thread.sleep(500);
            
            // 手动确认消息
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
            System.out.println("队列1 Topic消息处理完成");
            
        } catch (Exception e) {
            System.err.println("队列1处理Topic消息失败: " + e.getMessage());
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
    
    /**
     * 消费Topic消息 - 队列2
     */
    @RabbitListener(queues = RabbitConfig.TOPIC_QUEUE_2)
    public void consumeTopicMessage2(MessageDto message, Message msg, Channel channel) throws IOException {
        try {
            System.out.println("队列2接收到Topic消息: " + message);
            
            // 模拟业务处理
            Thread.sleep(500);
            
            // 手动确认消息
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
            System.out.println("队列2 Topic消息处理完成");
            
        } catch (Exception e) {
            System.err.println("队列2处理Topic消息失败: " + e.getMessage());
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
    
    /**
     * 消费Fanout消息 - 队列1
     */
    @RabbitListener(queues = RabbitConfig.FANOUT_QUEUE_1)
    public void consumeFanoutMessage1(MessageDto message, Message msg, Channel channel) throws IOException {
        try {
            System.out.println("Fanout队列1接收到消息: " + message);
            
            // 模拟业务处理
            Thread.sleep(300);
            
            // 手动确认消息
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
            System.out.println("Fanout队列1消息处理完成");
            
        } catch (Exception e) {
            System.err.println("Fanout队列1处理消息失败: " + e.getMessage());
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
    
    /**
     * 消费Fanout消息 - 队列2
     */
    @RabbitListener(queues = RabbitConfig.FANOUT_QUEUE_2)
    public void consumeFanoutMessage2(MessageDto message, Message msg, Channel channel) throws IOException {
        try {
            System.out.println("Fanout队列2接收到消息: " + message);
            
            // 模拟业务处理
            Thread.sleep(300);
            
            // 手动确认消息
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
            System.out.println("Fanout队列2消息处理完成");
            
        } catch (Exception e) {
            System.err.println("Fanout队列2处理消息失败: " + e.getMessage());
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
    
    /**
     * 消费死信消息
     */
    @RabbitListener(queues = RabbitConfig.DLX_QUEUE)
    public void consumeDlxMessage(MessageDto message, Message msg, Channel channel) throws IOException {
        try {
            System.out.println("接收到死信消息: " + message);
            
            // 处理死信消息的业务逻辑
            // 比如记录日志、发送告警、人工处理等
            
            // 手动确认消息
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
            System.out.println("死信消息处理完成");
            
        } catch (Exception e) {
            System.err.println("处理死信消息失败: " + e.getMessage());
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, false);
        }
    }
}

🎮 Controller层

java 复制代码
package com.example.demo.controller;

import com.example.demo.service.MessageProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/rabbitmq")
@CrossOrigin(origins = "*")
public class RabbitMQController {
    
    @Autowired
    private MessageProducer messageProducer;
    
    /**
     * 发送Direct消息
     */
    @PostMapping("/direct")
    public ResponseEntity<Map<String, String>> sendDirectMessage(@RequestBody Map<String, String> request) {
        String content = request.get("content");
        messageProducer.sendDirectMessage(content);
        
        Map<String, String> response = new HashMap<>();
        response.put("status", "success");
        response.put("message", "Direct消息发送成功");
        return ResponseEntity.ok(response);
    }
    
    /**
     * 发送Topic消息
     */
    @PostMapping("/topic")
    public ResponseEntity<Map<String, String>> sendTopicMessage(@RequestBody Map<String, String> request) {
        String content = request.get("content");
        String routingKey = request.getOrDefault("routingKey", "topic.test.message");
        
        messageProducer.sendTopicMessage(routingKey, content);
        
        Map<String, String> response = new HashMap<>();
        response.put("status", "success");
        response.put("message", "Topic消息发送成功");
        response.put("routingKey", routingKey);
        return ResponseEntity.ok(response);
    }
    
    /**
     * 发送Fanout消息
     */
    @PostMapping("/fanout")
    public ResponseEntity<Map<String, String>> sendFanoutMessage(@RequestBody Map<String, String> request) {
        String content = request.get("content");
        messageProducer.sendFanoutMessage(content);
        
        Map<String, String> response = new HashMap<>();
        response.put("status", "success");
        response.put("message", "Fanout消息发送成功");
        return ResponseEntity.ok(response);
    }
    
    /**
     * 发送延迟消息
     */
    @PostMapping("/delay")
    public ResponseEntity<Map<String, String>> sendDelayMessage(@RequestBody Map<String, Object> request) {
        String content = (String) request.get("content");
        Integer delaySeconds = (Integer) request.getOrDefault("delaySeconds", 10);
        
        messageProducer.sendDelayMessage(content, delaySeconds);
        
        Map<String, String> response = new HashMap<>();
        response.put("status", "success");
        response.put("message", "延迟消息发送成功");
        response.put("delaySeconds", delaySeconds.toString());
        return ResponseEntity.ok(response);
    }
    
    /**
     * 发送优先级消息
     */
    @PostMapping("/priority")
    public ResponseEntity<Map<String, String>> sendPriorityMessage(@RequestBody Map<String, Object> request) {
        String content = (String) request.get("content");
        Integer priority = (Integer) request.getOrDefault("priority", 0);
        
        messageProducer.sendPriorityMessage(content, priority);
        
        Map<String, String> response = new HashMap<>();
        response.put("status", "success");
        response.put("message", "优先级消息发送成功");
        response.put("priority", priority.toString());
        return ResponseEntity.ok(response);
    }
}

📊 最佳实践

1. 消息可靠性

  • 开启生产者确认机制
  • 使用持久化队列和消息
  • 实现消费者手动确认
  • 配置死信队列处理失败消息

2. 性能优化

  • 合理设置预取数量
  • 使用批量操作
  • 优化序列化方式
  • 监控队列长度

3. 高可用性

  • 配置集群模式
  • 使用镜像队列
  • 实现故障转移
  • 监控系统状态

本文关键词: RabbitMQ, 消息队列, AMQP, 异步通信, 微服务, 解耦