Spring Boot + RabbitMQ 实现异步消息处理(订单通知、邮件发送)!告别同步阻塞“噩梦”

🛠️ 一、准备工作(只需 3 分钟)

你需要:

  • JDK 17
  • IDEA
  • RabbitMQ(本地或 Docker)
  • 一个邮箱用于测试邮件发送

💡 没有 RabbitMQ? 用 Docker 一行命令启动:

复制代码
docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:3.11-management

访问:http://localhost:15672(默认账号密码:guest/guest)

💻 二、RabbitMQ 核心概念(简单理解)

三个核心组件:

  • Producer(生产者):发送消息的程序
  • Queue(队列):存储消息的地方
  • Consumer(消费者):处理消息的程序

交换机类型(4 种):

  • Direct:精确匹配路由键
  • Topic:通配符匹配路由键
  • Fanout:广播,忽略路由键
  • Headers:根据消息头匹配

📦 三、Spring Boot 集成 RabbitMQ(完整代码)

步骤 1:添加依赖(pom.xml)

XML 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- RabbitMQ Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    
    <!-- 邮件发送 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

步骤 2:配置 RabbitMQ(application.yml)

XML 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    
    # 消息确认配置
    publisher-confirm-type: correlated  # 发送确认
    publisher-returns: true             # 消息返回
    template:
      mandatory: true                   # 强制确认
    
    # 消费者配置
    listener:
      simple:
        acknowledge-mode: manual        # 手动确认
        concurrency: 3                  # 并发消费者数
        max-concurrency: 10             # 最大并发数

  # 邮件配置(测试用)
  mail:
    host: smtp.163.com
    username: your-email@163.com
    password: your-app-password        # 邮箱应用密码
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true

步骤 3:创建消息实体类(entity/OrderMessage.java)

java 复制代码
package com.example.rabbitmqdemo.entity;

import java.io.Serializable;

public class OrderMessage implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private Long orderId;
    private String customerEmail;
    private String productName;
    private Double amount;
    private String orderStatus;

    // 构造函数(省略)
    public OrderMessage() {}
    public OrderMessage(Long orderId, String customerEmail, String productName, Double amount) {
        this.orderId = orderId;
        this.customerEmail = customerEmail;
        this.productName = productName;
        this.amount = amount;
        this.orderStatus = "CREATED";
    }

    // getter/setter...
    public Long getOrderId() { return orderId; }
    public void setOrderId(Long orderId) { this.orderId = orderId; }
    public String getCustomerEmail() { return customerEmail; }
    public void setCustomerEmail(String customerEmail) { this.customerEmail = customerEmail; }
    public String getProductName() { return productName; }
    public void setProductName(String productName) { this.productName = productName; }
    public Double getAmount() { return amount; }
    public void setAmount(Double amount) { this.amount = amount; }
    public String getOrderStatus() { return orderStatus; }
    public void setOrderStatus(String orderStatus) { this.orderStatus = orderStatus; }
}

步骤 4:创建 RabbitMQ 配置(config/RabbitConfig.java)

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

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

@Configuration
public class RabbitConfig {

    @Value("${mq.exchange.order:order-exchange}")
    private String orderExchange;

    @Value("${mq.queue.order:order-queue}")
    private String orderQueue;

    @Value("${mq.routing-key.order:order.created}")
    private String orderRoutingKey;

    // 声明交换机
    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange(orderExchange, true, false);
    }

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

    // 绑定交换机和队列
    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue())
                .to(orderExchange())
                .with(orderRoutingKey);
    }
}

配置说明

  • DirectExchange:直连交换机
  • QueueBuilder.durable():持久化队列
  • BindingBuilder:绑定关系

步骤 5:创建消息生产者(service/OrderProducer.java)

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

import com.example.rabbitmqdemo.entity.OrderMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class OrderProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Value("${mq.exchange.order:order-exchange}")
    private String orderExchange;

    @Value("${mq.routing-key.order:order.created}")
    private String orderRoutingKey;

    /**
     * 发送订单创建消息
     */
    public void sendOrderCreatedMessage(OrderMessage orderMessage) {
        // 设置消息属性(可选)
        rabbitTemplate.convertAndSend(orderExchange, orderRoutingKey, orderMessage);
        System.out.println("订单消息已发送:" + orderMessage.getOrderId());
    }
}

步骤 6:创建消息消费者(service/OrderConsumer.java)

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

import com.example.rabbitmqdemo.entity.OrderMessage;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service;

import java.io.IOException;

@Service
public class OrderConsumer {

    @Autowired
    private JavaMailSender mailSender;

    /**
     * 订单创建消息消费者
     */
    @RabbitListener(queues = "${mq.queue.order:order-queue}")
    public void handleOrderCreated(OrderMessage orderMessage, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
        try {
            System.out.println("收到订单消息:" + orderMessage.getOrderId());

            // 1. 发送订单确认邮件
            sendOrderEmail(orderMessage);

            // 2. 更新用户积分(这里简化)
            updateUserPoints(orderMessage.getOrderId());

            // 3. 记录日志
            logOrderProcessed(orderMessage.getOrderId());

            // 手动确认消息
            channel.basicAck(deliveryTag, false);
            System.out.println("订单消息处理完成:" + orderMessage.getOrderId());

        } catch (Exception e) {
            System.err.println("处理订单消息失败:" + e.getMessage());
            try {
                // 拒绝消息,重新入队(这里简化,实际项目可能需要死信队列)
                channel.basicNack(deliveryTag, false, true);
            } catch (IOException ioException) {
                e.printStackTrace();
            }
        }
    }

    private void sendOrderEmail(OrderMessage orderMessage) {
        try {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setTo(orderMessage.getCustomerEmail());
            message.setSubject("订单创建成功");
            message.setText("您的订单 " + orderMessage.getOrderId() + 
                          " 已创建成功,商品:" + orderMessage.getProductName() + 
                          ",金额:" + orderMessage.getAmount() + "元");
            mailSender.send(message);
            System.out.println("订单邮件已发送给:" + orderMessage.getCustomerEmail());
        } catch (Exception e) {
            System.err.println("发送邮件失败:" + e.getMessage());
        }
    }

    private void updateUserPoints(Long orderId) {
        // 模拟更新用户积分
        System.out.println("为订单 " + orderId + " 更新用户积分");
    }

    private void logOrderProcessed(Long orderId) {
        // 模拟记录处理日志
        System.out.println("订单 " + orderId + " 处理日志已记录");
    }
}

步骤 7:创建订单控制器(controller/OrderController.java)

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

import com.example.rabbitmqdemo.entity.OrderMessage;
import com.example.rabbitmqdemo.service.OrderProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @Autowired
    private OrderProducer orderProducer;

    /**
     * 创建订单(异步处理)
     */
    @PostMapping("/create")
    public Map<String, Object> createOrder(@RequestParam String customerEmail,
                                          @RequestParam String productName,
                                          @RequestParam Double amount) {
        // 1. 保存订单到数据库(这里简化)
        Long orderId = System.currentTimeMillis(); // 模拟生成订单ID

        // 2. 创建订单消息
        OrderMessage orderMessage = new OrderMessage(orderId, customerEmail, productName, amount);

        // 3. 发送消息到 RabbitMQ(异步)
        orderProducer.sendOrderCreatedMessage(orderMessage);

        // 4. 立即返回,不等待异步处理完成
        Map<String, Object> result = new HashMap<>();
        result.put("success", true);
        result.put("message", "订单创建成功,异步处理中");
        result.put("orderId", orderId);
        return result;
    }
}

▶️ 四、运行验证(30 秒搞定)

1. 启动 RabbitMQ

确保 RabbitMQ 已启动并能访问

2. 启动 Spring Boot 项目

bash 复制代码
mvn spring-boot:run

3. 测试接口

bash 复制代码
POST http://localhost:8080/api/orders/create
Content-Type: application/x-www-form-urlencoded

customerEmail=test@example.com&productName=iPhone&amount=5999.00

4. 预期结果

  • 接口立即返回:{"success":true,"message":"订单创建成功,异步处理中","orderId":...}
  • 控制台打印:订单消息已发送:...
  • 消费者处理:收到订单消息:...订单邮件已发送给:...
  • 邮箱收到订单确认邮件

✅ 订单创建响应时间从几秒缩短到几百毫秒!

五、Bonus:消息队列常见坑点

坑 1:消息丢失

  • 发送端 :开启 publisher-confirm-type 确认发送成功
  • 消费端 :手动确认 channel.basicAck(),处理失败时拒绝

坑 2:消息重复

  • 消费者:实现幂等性处理(如根据订单ID去重)
  • 生产者:避免重复发送

坑 3:死信队列

  • 场景:消息处理失败且无法恢复
  • 配置:设置死信交换机和队列,人工处理

坑 4:消息顺序

  • 问题:RabbitMQ 不保证全局顺序
  • 解决方案:相同业务的消息用相同 routing key,确保进入同一队列

六、进阶:延迟消息(订单超时取消)

java 复制代码
// 添加延迟队列配置
@Bean
public Queue delayQueue() {
    return QueueBuilder.durable("order.delay.queue")
            .withArgument("x-dead-letter-exchange", orderExchange) // 死信交换机
            .withArgument("x-dead-letter-routing-key", "order.timeout") // 死信路由键
            .build();
}

// 发送延迟消息(15分钟后触发)
public void sendOrderDelayMessage(OrderMessage orderMessage, long delayTime) {
    rabbitTemplate.convertAndSend("order.delay.exchange", "order.delay", orderMessage, 
        message -> {
            message.getMessageProperties().setDelay((int) delayTime);
            return message;
        });
}

七、写在最后

消息队列,看似高深,实则是 解耦 + 异步 + 削峰 的组合。

记住三句话:

  • 异步处理提升响应速度
  • 手动确认保证消息不丢
  • 幂等处理防止重复消费

学会消息队列,你就掌握了 高并发系统设计的核心技能

相关推荐
喝养乐多长不高1 小时前
RabbitMQ:消息确认
java·rabbitmq·java-rabbitmq
y***61316 小时前
【springboot】Spring 官方抛弃了 Java 8!新idea如何创建java8项目
java·spring boot·spring
tanxinji6 小时前
RabbitMQ四种交换器类型详解及示例
java·rabbitmq
Filotimo_10 小时前
SpringBoot3整合Druid数据源
java·spring boot
程序猿202310 小时前
项目结构深度解析:理解Spring Boot项目的标准布局和约定
java·spring boot·后端
2501_9411477110 小时前
Python、JavaScript 和 Ruby:三种动态编程语言的特点与应用
rabbitmq
Java开发追求者12 小时前
vscode导入springboot项目
java·ide·spring boot·vscode
熊猫比分站12 小时前
让电竞数据实时跳动:Spring Boot 后端 + Vue 前端的完美融合实践
前端·vue.js·spring boot
Wilson Chen13 小时前
Spring Boot 多级缓存实现与优化:从本地缓存到 Redis
spring boot·redis·缓存