SpringBoot 整合 RabbitMQ 消息队列——异步解耦的利器

消息队列是后端开发的核心组件,用于异步处理、削峰填谷、服务解耦。RabbitMQ 是目前使用最广泛的消息中间件之一,这篇文章带你从零上手。

一、为什么要用消息队列

场景:用户注册后发送邮件

java 复制代码
// 同步方式:注册完等邮件发完才返回
public void register(User user) {
    userMapper.insert(user);       // 10ms
    emailService.sendEmail(user);  // 2000ms(等待邮件发送)
    return "注册成功";
}
// 总耗时:~2010ms
java 复制代码
// 异步方式:注册完丢到消息队列就返回
public void register(User user) {
    userMapper.insert(user);                // 10ms
    rabbitTemplate.convertAndSend("email", user);  // 5ms
    return "注册成功";
}
// 总耗时:~15ms 立即返回,邮件异步发送
场景 同步 异步(消息队列)
注册发邮件 2秒 15ms ✅
秒杀/抢购 可能崩溃 平稳削峰 ✅
订单处理 耦合严重 服务解耦 ✅

二、核心概念

复制代码
 Producer → Exchange → Queue → Consumer
(生产者)  (交换机)  (队列)  (消费者)
概念 说明
Producer 发送消息的一方(如注册服务)
Exchange 消息路由器,决定消息发到哪个队列
Queue 存储消息的队列
Consumer 消费消息的一方(如邮件服务)

三、环境准备

1. 安装 RabbitMQ(Windows)

下载安装后,访问 http://localhost:15672 (默认账号:guest/guest)

2. 引入依赖

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

3. 配置

yaml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual  # 手动确认
        prefetch: 1               # 每次只消费一条

四、实战:注册发送邮件

1. 配置队列和交换机

java 复制代码
@Configuration
public class RabbitConfig {

    // 队列
    @Bean
    public Queue emailQueue() {
        return QueueBuilder.durable("email.queue").build();
    }

    // 交换机
    @Bean
    public DirectExchange emailExchange() {
        return new DirectExchange("email.exchange");
    }

    // 绑定:队列通过 routing key 绑定到交换机
    @Bean
    public Binding emailBinding() {
        return BindingBuilder.bind(emailQueue())
                .to(emailExchange())
                .with("email.routing");
    }
}

2. 发送消息

java 复制代码
@Service
public class UserService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void register(User user) {
        // 1. 保存用户到数据库
        userMapper.insert(user);

        // 2. 发送消息到队列(异步发邮件)
        Map<String, Object> msg = new HashMap<>();
        msg.put("email", user.getEmail());
        msg.put("username", user.getUsername());
        msg.put("type", "welcome");

        rabbitTemplate.convertAndSend(
            "email.exchange",      // 交换机
            "email.routing",       // routing key
            msg                    // 消息内容
        );

        System.out.println("注册成功,邮件已投递到队列");
    }
}

3. 消费消息

java 复制代码
@Component
@Slf4j
public class EmailConsumer {

    @RabbitListener(queues = "email.queue")
    public void handleEmail(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            // 获取消息内容
            Map<String, Object> msg = (Map<String, Object>) 
                new ObjectInputStream(
                    new ByteArrayInputStream(message.getBody())
                ).readObject();

            String email = (String) msg.get("email");
            String username = (String) msg.get("username");

            // 模拟发送邮件
            System.out.println("发送欢迎邮件给: " + email);
            Thread.sleep(1000);  // 模拟邮件发送耗时

            // 手动确认(告诉 RabbitMQ 这条消息处理完了)
            channel.basicAck(deliveryTag, false);

        } catch (Exception e) {
            log.error("邮件发送失败", e);
            // 失败后,重回队列或进入死信队列
            channel.basicNack(deliveryTag, false, true);
        }
    }
}

五、交换机类型

1. Direct Exchange(直连)

根据 routing key 精确匹配:

java 复制代码
// 队列绑定 routing key = "email"
// 消息 routing key = "email"   ✅ 匹配
// 消息 routing key = "sms"     ❌ 不匹配

2. Topic Exchange(主题)

支持通配符匹配:

java 复制代码
// 队列绑定 routing key = "log.*"
// 消息 routing key = "log.info"   ✅ 匹配
// 消息 routing key = "log.error"  ✅ 匹配
// 消息 routing key = "user.log"   ❌ 不匹配

// * 代表一个单词,# 代表多个单词
// 队列绑定 "log.#"
// 能匹配 log.info、log.error、log.user.info

3. Fanout Exchange(广播)

发送给所有绑定的队列,忽略 routing key:

java 复制代码
// 适合群发通知、广播消息
rabbitTemplate.convertAndSend("notice.exchange", "", message);

六、消息可靠性保障

1. 生产者确认(确保消息到达交换机)

yaml 复制代码
spring:
  rabbitmq:
    publisher-confirm-type: correlated  # 开启确认
    publisher-returns: true             # 开启回退
java 复制代码
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory factory) {
    RabbitTemplate template = new RabbitTemplate(factory);
    template.setConfirmCallback((data, ack, cause) -> {
        if (!ack) {
            System.out.println("消息发送到交换机失败: " + cause);
        }
    });
    return template;
}

2. 消费者手动确认(确保消息被处理)

java 复制代码
// 处理成功 → 确认
channel.basicAck(deliveryTag, false);

// 处理失败 → 重回队列重试
channel.basicNack(deliveryTag, false, true);

// 处理失败 → 丢弃消息(进入死信队列)
channel.basicNack(deliveryTag, false, false);

3. 死信队列(处理失败的消息)

java 复制代码
@Bean
public Queue deadLetterQueue() {
    return QueueBuilder.durable("email.dead.letter").build();
}

@Bean
public Queue emailQueue() {
    Map<String, Object> args = new HashMap<>();
    args.put("x-dead-letter-exchange", "dead.letter.exchange");
    args.put("x-dead-letter-routing-key", "dead.letter.routing");
    return QueueBuilder.durable("email.queue").withArguments(args).build();
}

七、应用场景总结

场景 推荐交换机 说明
业务消息(注册、下单) Direct 一对一精确路由
日志收集 Topic 按级别/模块分类
系统通知广播 Fanout 所有服务同时收到
延迟任务 死信队列 订单超时取消

总结

RabbitMQ 是后端开发的必备中间件。记住三条原则:

  1. 异步处理提升响应速度------耗时操作丢到队列,立即返回
  2. 手动确认保证不丢消息------处理成功才 ack
  3. 死信队列兜底------处理失败的消息有地方去

如果对你有帮助,欢迎点赞、评论、关注【张老师技术栈】,持续分享 Java/Python/爬虫 实战干货。