SpringBoot + RabbitMQ 整合配置与案例详解

195 阅读6分钟

SpringBoot + RabbitMQ 整合配置与案例详解

一、环境准备

1. 安装RabbitMQ

使用Docker安装RabbitMQ(推荐):

docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin rabbitmq:management

2. 创建SpringBoot项目

使用Spring Initializer创建项目,选择以下依赖:

  • Spring Web
  • Spring for RabbitMQ

二、基础配置

1. Maven依赖(pom.xml)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2. 配置文件(application.yml)

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin
    virtual-host: /
    # 生产者确认配置
    publisher-confirm-type: correlated  # 开启发送确认机制
    publisher-returns: true  # 开启发送失败退回机制
    template:
      mandatory: true  # 设置交换机处理失败消息的模式
    # 消费者配置
    listener:
      simple:
        acknowledge-mode: manual  # 手动确认模式
        prefetch: 1  # 每次只处理一条消息
        concurrency: 1  # 最小并发消费者数量
        max-concurrency: 5  # 最大并发消费者数量

三、基础功能实现

1. 交换机和队列配置类

package com.example.rabbitmq.config;

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

@Configuration
public class RabbitMQConfig {
    // 交换机名称
    public static final String BOOT_EXCHANGE = "boot_exchange";
    // 队列名称
    public static final String BOOT_QUEUE = "boot_queue";
    // 路由键
    public static final String BOOT_ROUTING_KEY = "boot.info";

    // 声明交换机
    @Bean("bootExchange")
    public Exchange bootExchange() {
        return ExchangeBuilder.directExchange(BOOT_EXCHANGE)
                .durable(true)  // 持久化
                .build();
    }

    // 声明队列
    @Bean("bootQueue")
    public Queue bootQueue() {
        return QueueBuilder.durable(BOOT_QUEUE)
                .build();
    }

    // 绑定交换机和队列
    @Bean
    public Binding bindQueueExchange(
            @Qualifier("bootQueue") Queue queue,
            @Qualifier("bootExchange") Exchange exchange) {
        return BindingBuilder.bind(queue)
                .to(exchange)
                .with(BOOT_ROUTING_KEY)
                .noargs();
    }
}

2. 生产者(消息发送)

2.1 配置RabbitTemplate
package com.example.rabbitmq.config;

import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitTemplateConfig {

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        
        // 设置确认回调
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack) {
                System.out.println("消息发送成功,correlationId: " + correlationData.getId());
            } else {
                System.out.println("消息发送失败,原因: " + cause);
                // 可以在这里进行重试或记录日志
            }
        });
        
        // 设置返回回调
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            System.out.println("消息投递到队列失败!");
            System.out.println("消息: " + new String(message.getBody()));
            System.out.println("响应码: " + replyCode);
            System.out.println("响应信息: " + replyText);
            System.out.println("交换机: " + exchange);
            System.out.println("路由键: " + routingKey);
        });
        
        return rabbitTemplate;
    }
}
2.2 消息发送服务
package com.example.rabbitmq.service;

import com.example.rabbitmq.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.connection.CorrelationData;
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 MessageService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 发送消息
    public void sendMessage(String message) {
        // 生成消息ID
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        
        System.out.println("发送消息: " + message + ",correlationId: " + correlationData.getId());
        
        // 发送消息
        rabbitTemplate.convertAndSend(
                RabbitMQConfig.BOOT_EXCHANGE,  // 交换机
                RabbitMQConfig.BOOT_ROUTING_KEY,  // 路由键
                message,  // 消息内容
                correlationData  // 关联数据
        );
    }
}
2.3 控制器
package com.example.rabbitmq.controller;

import com.example.rabbitmq.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MessageController {

    @Autowired
    private MessageService messageService;

    @GetMapping("/send")
    public String sendMessage(@RequestParam String message) {
        messageService.sendMessage(message);
        return "消息发送成功";
    }
}

3. 消费者(消息接收)

package com.example.rabbitmq.consumer;

import com.example.rabbitmq.config.RabbitMQConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class MessageConsumer {

    @RabbitListener(queues = RabbitMQConfig.BOOT_QUEUE)
    public void receiveMessage(String message, Channel channel, Message msg) throws IOException {
        try {
            System.out.println("接收到消息: " + message);
            
            // 处理业务逻辑
            processMessage(message);
            
            // 手动确认消息
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            System.out.println("消息处理失败: " + e.getMessage());
            
            // 拒绝消息并重新入队(可以根据业务需要选择是否重新入队)
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, false);
        }
    }
    
    private void processMessage(String message) {
        // 模拟业务处理
        System.out.println("处理消息中..." + message);
    }
}

四、高级特性实现

1. 死信队列配置

package com.example.rabbitmq.config;

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

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

@Configuration
public class DeadLetterConfig {
    // 普通交换机
    public static final String NORMAL_EXCHANGE = "normal_exchange";
    // 普通队列
    public static final String NORMAL_QUEUE = "normal_queue";
    // 死信交换机
    public static final String DEAD_EXCHANGE = "dead_exchange";
    // 死信队列
    public static final String DEAD_QUEUE = "dead_queue";
    // 路由键
    public static final String NORMAL_ROUTING_KEY = "normal.key";
    public static final String DEAD_ROUTING_KEY = "dead.key";

    // 声明死信交换机
    @Bean
    public Exchange deadExchange() {
        return ExchangeBuilder.directExchange(DEAD_EXCHANGE).durable(true).build();
    }

    // 声明死信队列
    @Bean
    public Queue deadQueue() {
        return QueueBuilder.durable(DEAD_QUEUE).build();
    }

    // 声明普通交换机
    @Bean
    public Exchange normalExchange() {
        return ExchangeBuilder.directExchange(NORMAL_EXCHANGE).durable(true).build();
    }

    // 声明普通队列并绑定死信交换机
    @Bean
    public Queue normalQueue() {
        Map<String, Object> args = new HashMap<>();
        // 设置死信交换机
        args.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        // 设置死信路由键
        args.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
        // 设置队列消息过期时间(10秒)
        args.put("x-message-ttl", 10000);
        // 设置队列最大长度
        args.put("x-max-length", 10);
        
        return QueueBuilder.durable(NORMAL_QUEUE).withArguments(args).build();
    }

    // 绑定普通队列和普通交换机
    @Bean
    public Binding normalBinding() {
        return BindingBuilder.bind(normalQueue()).to(normalExchange()).with(NORMAL_ROUTING_KEY).noargs();
    }

    // 绑定死信队列和死信交换机
    @Bean
    public Binding deadBinding() {
        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(DEAD_ROUTING_KEY).noargs();
    }
}

2. 延迟队列实现(基于死信队列)

2.1 发送延迟消息
@Service
public class DelayMessageService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 发送延迟消息
    public void sendDelayMessage(String message, long delayMillis) {
        // 创建消息属性
        MessageProperties properties = new MessageProperties();
        // 设置消息过期时间
        properties.setExpiration(String.valueOf(delayMillis));
        
        // 创建消息
        Message msg = new Message(message.getBytes(), properties);
        
        System.out.println("发送延迟消息: " + message + ",延迟时间: " + delayMillis + "ms");
        
        // 发送到普通队列
        rabbitTemplate.send(DeadLetterConfig.NORMAL_EXCHANGE, DeadLetterConfig.NORMAL_ROUTING_KEY, msg);
    }
}
2.2 死信队列消费者
@Component
public class DeadLetterConsumer {

    @RabbitListener(queues = DeadLetterConfig.DEAD_QUEUE)
    public void receiveDeadLetter(String message, Channel channel, Message msg) throws IOException {
        try {
            System.out.println("接收到死信消息: " + message + ",时间: " + new Date());
            
            // 处理死信消息业务逻辑
            processDeadLetter(message);
            
            // 手动确认
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            System.out.println("死信消息处理失败: " + e.getMessage());
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, false);
        }
    }
    
    private void processDeadLetter(String message) {
        // 模拟业务处理
        System.out.println("处理死信消息中..." + message);
    }
}

五、消息可靠性保障

1. 生产者端保障

  • 确认机制(Publisher Confirms):确保消息到达交换机
  • 退回机制(Publisher Returns):确保消息从交换机路由到队列
  • 事务机制:通过事务确保消息发送和业务操作的原子性(性能较低,不推荐)
  • 本地消息表:将消息持久化到数据库,通过定时任务重试发送失败的消息

2. 服务端保障

  • 交换机持久化:durable=true
  • 队列持久化:durable=true
  • 消息持久化:设置消息投递模式为2(MessageProperties.PERSISTENT_TEXT_PLAIN
  • 高可用集群:部署RabbitMQ集群,避免单点故障

3. 消费者端保障

  • 手动确认:acknowledge-mode: manual,处理完业务后再确认
  • 限流:通过prefetch控制消费者一次性获取的消息数量
  • 幂等性处理:确保重复消费不会导致业务异常

六、测试接口

1. 测试基础消息发送

GET http://localhost:8080/send?message=Hello%20RabbitMQ

2. 测试延迟消息(需要添加对应的Controller)

@GetMapping("/sendDelay")
public String sendDelayMessage(@RequestParam String message, @RequestParam long delayMillis) {
    delayMessageService.sendDelayMessage(message, delayMillis);
    return "延迟消息发送成功,延迟时间:" + delayMillis + "ms";
}

七、常见问题及解决方案

1. 消息丢失

  • 检查确认机制是否开启
  • 确认交换机、队列、消息是否都设置了持久化
  • 检查消费者是否正确确认消息

2. 消息重复消费

  • 实现幂等性处理(如基于消息ID去重)
  • 使用Redis或数据库记录已处理的消息ID

3. 消息积压

  • 增加消费者数量(调整concurrency参数)
  • 优化消费者处理逻辑,提高消费速度
  • 设置合适的预取数量(prefetch)

4. 死信队列不生效

  • 检查TTL设置是否正确
  • 确认死信交换机和路由键配置正确
  • 确保队列没有消费者

八、启动类

package com.example.rabbitmq;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RabbitmqApplication {

    public static void main(String[] args) {
        SpringApplication.run(RabbitmqApplication.class, args);
    }

}

通过以上配置和代码,您可以实现一个功能完整的SpringBoot + RabbitMQ应用,包括基础的消息发送接收、可靠性保障、死信队列和延迟队列等高级特性。

九、代码链接

gitee.com/tree_boss/s…