消息驱动Spring Cloud Stream与RabbitMQ整合

消息驱动 – 不用打电话,写信也能办事

零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目

资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。

一、先白话白话消息队列有啥用

场景1:用户注册发短信

以前的做法(同步调用):

public void register(User user) {
    // 1. 保存用户(50ms)
    userService.save(user);
    // 2. 发欢迎短信(2000ms)
    smsService.sendWelcomeSms(user.getPhone());
    // 3. 送优惠券(100ms)
    couponService.sendCoupon(user.getId());
    // 总耗时:2150ms,用户等2秒多!
}

问题:发短信慢,用户得等着!

现在的做法(异步消息):

public void register(User user) {
    // 1. 保存用户(50ms)
    userService.save(user);
    // 2. 发消息到队列(5ms)
    messageQueue.send("user.register", user);
    // 总耗时:55ms,用户秒响应!
    // 短信服务、优惠券服务自己从队列取消息处理
    // 用户不用等了!
}

场景2:双11下单

问题:瞬间千万人下单,数据库撑不住!
解决:订单先放消息队列,慢慢处理,这叫削峰填谷。

零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目

资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。

场景3:订单状态变更

问题:好多服务关心订单状态:

  • 物流服务:发货要通知
  • 库存服务:扣库存要通知
  • 客服系统:退款要通知
  • 数据分析:统计要数据

解决:订单服务发个消息,谁关心谁自己听。

二、消息队列选哪个?

消息队列 优点 缺点 适用场景
RabbitMQ 成熟稳定,功能全 性能一般,Erlang语言 企业级应用,复杂路由
Kafka 吞吐量高,持久化好 功能简单,配置复杂 日志收集,大数据
RocketMQ 阿里出品,功能全 文档一般,社区小 电商,金融
ActiveMQ 老牌,支持协议多 性能差,不活跃 老系统升级

咱们用RabbitMQ,因为:

  1. Spring Cloud Stream支持好
  2. 管理界面漂亮
  3. 功能全(路由、死信、延迟)
  4. 社区活跃

三、RabbitMQ核心概念(得先明白)

1. 生产者(Producer)

  • 发消息的
  • 就像写信的人

2. 消费者(Consumer)

  • 收消息的
  • 就像收信的人

3. 消息(Message)

  • 信的内容
  • 有header(信封)和body(信纸)

4. 交换机(Exchange)

  • 邮局
  • 决定信往哪寄

5. 队列(Queue)

  • 邮箱
  • 信先放这,等人来取

6. 绑定(Binding)

  • 邮局和邮箱的关系
  • 告诉邮局:这类信放这个邮箱

7. 路由键(Routing Key)

  • 信封上的地址
  • 邮局根据这个决定放哪个邮箱

四、安装RabbitMQ

方式1:Docker(推荐)

# 拉取镜像(带管理界面)
docker pull rabbitmq:3.12-management
# 运行
docker run -d \
  --name rabbitmq \
  -p 5672:5672 \      # 消息端口
  -p 15672:15672 \    # 管理界面端口
  -e RABBITMQ_DEFAULT_USER=admin \
  -e RABBITMQ_DEFAULT_PASS=admin123 \
  rabbitmq:3.12-management

方式2:直接安装

  1. 官网下载:https://www.rabbitmq.com
  2. 需要Erlang环境
  3. 安装完启动服务

访问管理界面

  1. 浏览器打开:http://localhost:15672
  2. 用户名:admin
  3. 密码:admin123
  4. 看到管理界面就中了

五、Spring Cloud Stream是啥?

不用直接操作RabbitMQ API,Spring Cloud Stream封装好了:

  • 生产者:用@EnableBinding(Source.class) + MessageChannel
  • 消费者:用@EnableBinding(Sink.class) + @StreamListener

新的方式(推荐,简单):

  • 生产者:用@EnableBinding + StreamBridge
  • 消费者:用@Bean定义Consumer

六、开整!用户注册发消息

步骤1:创建消息服务

新建项目:message-service

1. 加依赖

pom.xml:

<!-- Spring Cloud Stream RabbitMQ -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<!-- Nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2. 配置文件

application.yml:

server:
  port: 8083
spring:
  application:
    name: message-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    stream:
      # 绑定器(用RabbitMQ)
      binders:
        rabbit:
          type: rabbit
          environment:
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: admin
                password: admin123
                virtual-host: /  # 虚拟主机,相当于数据库
      # 绑定配置
      bindings:
        # 输出通道(发消息)
        userRegisterOutput:
          destination: user-register-exchange  # 交换机名
          content-type: application/json
          binder: rabbit
          group: user-register-group  # 消费者组,同一组只有一个消费者消费
        # 输入通道(收消息)
        userRegisterInput:
          destination: user-register-exchange
          content-type: application/json
          binder: rabbit
          group: user-register-group
          consumer:
            concurrency: 3  # 并发消费者数
        # 发短信的输入通道
        smsInput:
          destination: sms-exchange
          content-type: application/json
          binder: rabbit
        # 发优惠券的输入通道
        couponInput:
          destination: coupon-exchange
          content-type: application/json
          binder: rabbit
      # RabbitMQ配置
      rabbit:
        bindings:
          userRegisterOutput:
            producer:
              # 消息确认(确保消息不丢)
              confirm-type: correlated  # 异步确认
              # 消息持久化
              delivery-mode: persistent
          userRegisterInput:
            consumer:
              # 手动确认(确保消息处理完才删)
              acknowledge-mode: manual
              # 重试
              max-attempts: 3
              # 死信队列
              auto-bind-dlq: true  # 自动绑定死信队列
              dead-letter-queue-name: user-register-dlq
              dead-letter-exchange: user-register-dlq-exchange

步骤2:发消息(生产者)

1. 创建消息实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRegisterEvent {
    private Long userId;
    private String username;
    private String phone;
    private String email;
    private Date registerTime;
}
2. 创建生产者
@Service
@Slf4j
public class MessageProducer {
    @Autowired
    private StreamBridge streamBridge;  // Spring Cloud Stream提供的
    /**
     * 发送用户注册消息
     */
    public void sendUserRegisterEvent(UserRegisterEvent event) {
        log.info("发送用户注册消息:{}", event);
        // 方式1:用StreamBridge(简单)
        boolean sent = streamBridge.send("userRegisterOutput", event);
        if (sent) {
            log.info("消息发送成功");
        } else {
            log.error("消息发送失败");
            // 可以存数据库,定时重发
        }
        // 方式2:用MessageChannel(老方式)
        // Message<UserRegisterEvent> message = MessageBuilder
        //     .withPayload(event)
        //     .setHeader("message-type", "user-register")
        //     .build();
        // userRegisterOutputChannel.send(message);
    }
    /**
     * 发送短信消息
     */
    public void sendSmsEvent(String phone, String content) {
        Map<String, Object> event = new HashMap<>();
        event.put("phone", phone);
        event.put("content", content);
        event.put("sendTime", new Date());
        streamBridge.send("smsOutput", event);
    }
    /**
     * 发送优惠券消息
     */
    public void sendCouponEvent(Long userId, String couponType) {
        Map<String, Object> event = new HashMap<>();
        event.put("userId", userId);
        event.put("couponType", couponType);
        event.put("sendTime", new Date());
        streamBridge.send("couponOutput", event);
    }
}
3. 测试接口
@RestController
@RequestMapping("/message")
public class MessageController {
    @Autowired
    private MessageProducer messageProducer;
    @PostMapping("/send/register")
    public String sendRegisterMessage(@RequestParam String username,
                                      @RequestParam String phone) {
        UserRegisterEvent event = new UserRegisterEvent(
            1L, username, phone, "test@example.com", new Date()
        );
        messageProducer.sendUserRegisterEvent(event);
        return "注册消息发送成功";
    }
}

步骤3:收消息(消费者)

1. 短信消费者
@Service
@Slf4j
public class SmsConsumer {
    /**
     * 监听用户注册消息,发欢迎短信
     * 方法名就是Bean名,要跟配置的binding名对应
     */
    @Bean
    public Consumer<UserRegisterEvent> userRegisterInput() {
        return event -> {
            log.info("收到用户注册消息,准备发短信:{}", event);
            // 模拟发短信
            sendSms(event.getPhone(), 
                   "亲爱的" + event.getUsername() + ",欢迎注册!");
            log.info("短信发送完成");
        };
    }
    /**
     * 监听短信队列
     */
    @Bean
    public Consumer<Message<Map<String, Object>>> smsInput() {
        return message -> {
            Map<String, Object> payload = message.getPayload();
            String phone = (String) payload.get("phone");
            String content = (String) payload.get("content");
            log.info("收到短信消息,手机号:{},内容:{}", phone, content);
            // 实际发短信逻辑
            boolean success = sendSms(phone, content);
            if (success) {
                log.info("短信发送成功");
                // 手动确认(如果配置了手动确认)
                // Channel channel = (Channel) message.getHeaders().get(AmqpHeaders.CHANNEL);
                // Long deliveryTag = (Long) message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
                // channel.basicAck(deliveryTag, false);
            } else {
                log.error("短信发送失败");
                // 可以抛异常,触发重试
                // throw new RuntimeException("短信发送失败");
            }
        };
    }
    private boolean sendSms(String phone, String content) {
        log.info("发送短信到 {}:{}", phone, content);
        // 模拟发短信,成功概率90%
        return Math.random() > 0.1;
    }
}
2. 优惠券消费者
@Service
@Slf4j
public class CouponConsumer {
    /**
     * 监听用户注册消息,送优惠券
     */
    @Bean
    public Consumer<UserRegisterEvent> userRegisterInput() {
        return event -> {
            log.info("收到用户注册消息,准备送优惠券:{}", event);
            // 模拟送优惠券
            sendCoupon(event.getUserId(), "WELCOME_10");
            log.info("优惠券发放完成");
        };
    }
    /**
     * 监听优惠券队列
     */
    @Bean
    public Consumer<Message<Map<String, Object>>> couponInput() {
        return message -> {
            Map<String, Object> payload = message.getPayload();
            Long userId = ((Number) payload.get("userId")).longValue();
            String couponType = (String) payload.get("couponType");
            log.info("收到优惠券消息,用户ID:{},优惠券类型:{}", 
                    userId, couponType);
            // 实际发优惠券逻辑
            boolean success = sendCoupon(userId, couponType);
            if (!success) {
                // 发优惠券失败,重试3次后进入死信队列
                throw new RuntimeException("发优惠券失败");
            }
        };
    }
    private boolean sendCoupon(Long userId, String couponType) {
        log.info("给用户 {} 发放优惠券 {}", userId, couponType);
        // 模拟发优惠券,成功概率80%
        return Math.random() > 0.2;
    }
}

七、实际场景:订单创建流程

场景:用户下单后

  1. 扣库存(同步)
  2. 创建订单(同步)
  3. 发短信通知(异步)
  4. 更新销量(异步)
  5. 推荐系统(异步)
  6. 数据分析(异步)

实现:订单服务

@Service
@Slf4j
public class OrderServiceWithMQ {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private ProductService productService;
    @Autowired
    private MessageProducer messageProducer;
    @Transactional
    public Order createOrderWithMQ(Long userId, Long productId, Integer count) {
        // 1. 扣库存(同步,必须成功)
        boolean stockSuccess = productService.decreaseStock(productId, count);
        if (!stockSuccess) {
            throw new RuntimeException("库存不足");
        }
        // 2. 创建订单(同步)
        Order order = createOrder(userId, productId, count);
        // 3. 发送订单创建消息(异步)
        sendOrderCreatedEvent(order);
        return order;
    }
    private Order createOrder(Long userId, Long productId, Integer count) {
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(productId);
        order.setCount(count);
        order.setStatus(0);  // 待支付
        order.setCreateTime(new Date());
        orderMapper.insert(order);
        log.info("订单创建成功,订单ID:{}", order.getId());
        return order;
    }
    private void sendOrderCreatedEvent(Order order) {
        Map<String, Object> event = new HashMap<>();
        event.put("orderId", order.getId());
        event.put("userId", order.getUserId());
        event.put("productId", order.getProductId());
        event.put("amount", order.getTotalAmount());
        event.put("createTime", order.getCreateTime());
        // 发送到订单创建交换机
        messageProducer.sendOrderCreatedEvent(event);
        log.info("订单创建消息发送成功,订单ID:{}", order.getId());
    }
}

各个消费者处理:

// 短信服务消费者
@Component
@Slf4j
public class OrderSmsConsumer {
    @Bean
    public Consumer<Message<Map<String, Object>>> orderCreatedSmsInput() {
        return message -> {
            Map<String, Object> event = message.getPayload();
            Long orderId = (Long) event.get("orderId");
            Long userId = (Long) event.get("userId");
            log.info("收到订单创建消息,发送短信通知,订单ID:{}", orderId);
            // 查询用户手机号
            String phone = getUserPhone(userId);
            // 发送短信
            sendSms(phone, "您的订单已创建,订单号:" + orderId);
        };
    }
}
// 销量统计消费者
@Component
@Slf4j  
public class OrderStatConsumer {
    @Bean
    public Consumer<Message<Map<String, Object>>> orderCreatedStatInput() {
        return message -> {
            Map<String, Object> event = message.getPayload();
            Long productId = (Long) event.get("productId");
            Integer count = (Integer) event.get("count");
            log.info("更新商品销量,商品ID:{},数量:{}", productId, count);
            // 更新销量(这里可能更新Redis或数据库)
            updateProductSales(productId, count);
        };
    }
}
// 推荐系统消费者
@Component
@Slf4j
public class OrderRecommendConsumer {
    @Bean
    public Consumer<Message<Map<String, Object>>> orderCreatedRecommendInput() {
        return message -> {
            Map<String, Object> event = message.getPayload();
            Long userId = (Long) event.get("userId");
            Long productId = (Long) event.get("productId");
            log.info("更新用户偏好,用户ID:{},商品ID:{}", userId, productId);
            // 更新用户偏好
            updateUserPreference(userId, productId);
            // 实时推荐
            generateRecommendations(userId);
        };
    }
}

八、高级特性

1. 消息确认机制

spring:
  cloud:
    stream:
      rabbit:
        bindings:
          orderInput:
            consumer:
              acknowledge-mode: manual  # 手动确认
@Bean
public Consumer<Message<String>> orderInput() {
    return message -> {
        try {
            // 处理消息
            processOrder(message.getPayload());
            // 手动确认
            Channel channel = (Channel) message.getHeaders()
                .get(AmqpHeaders.CHANNEL);
            Long deliveryTag = (Long) message.getHeaders()
                .get(AmqpHeaders.DELIVERY_TAG);
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            // 拒绝消息,不放回队列(进入死信队列)
            Channel channel = (Channel) message.getHeaders()
                .get(AmqpHeaders.CHANNEL);
            Long deliveryTag = (Long) message.getHeaders()
                .get(AmqpHeaders.DELIVERY_TAG);
            channel.basicNack(deliveryTag, false, false);
        }
    };
}

2. 死信队列(DLQ)

消息处理失败时,进入死信队列:

spring:
  cloud:
    stream:
      rabbit:
        bindings:
          orderInput:
            consumer:
              auto-bind-dlq: true  # 自动创建死信队列
              dead-letter-queue-name: order-dlq
              dead-letter-exchange: order-dlq-exchange
              # 重试3次后进入死信队列
              max-attempts: 3
              # 重试间隔
              back-off-initial-interval: 1000
              back-off-multiplier: 2.0
              back-off-max-interval: 10000

3. 延迟消息(RabbitMQ插件)

安装延迟插件:

# 下载插件
# 放到RabbitMQ插件目录
# 启用插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

发送延迟消息:

public void sendDelayMessage() {
    Map<String, Object> headers = new HashMap<>();
    headers.put("x-delay", 60000);  // 延迟60秒
    Message<String> message = MessageBuilder
        .withPayload("延迟消息")
        .copyHeaders(headers)
        .build();
    streamBridge.send("delayOutput", message);
}

4. 消息重试

spring:
  cloud:
    stream:
      bindings:
        orderInput:
          consumer:
            max-attempts: 3  # 最大重试次数
            back-off-initial-interval: 1000  # 初始间隔1秒
            back-off-multiplier: 2.0  # 倍数递增
            back-off-max-interval: 10000  # 最大间隔10秒

九、消息队列的最佳实践

1. 消息设计

// 好的消息设计
@Data
public class OrderEvent {
    private String eventId;      // 消息ID(唯一)
    private String eventType;    // 事件类型:ORDER_CREATED
    private Long timestamp;      // 时间戳
    private String source;       // 来源服务
    private Object data;         // 业务数据
    private Map<String, Object> extra;  // 扩展字段
}

2. 幂等性处理

@Service
public class OrderConsumer {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Bean
    public Consumer<Message<OrderEvent>> orderInput() {
        return message -> {
            OrderEvent event = message.getPayload();
            // 1. 检查是否已处理(幂等)
            String key = "msg:" + event.getEventId();
            if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
                log.info("消息已处理,跳过:{}", event.getEventId());
                return;
            }
            // 2. 处理消息
            processOrder(event);
            // 3. 记录已处理
            redisTemplate.opsForValue()
                .set(key, "processed", 24, TimeUnit.HOURS);
        };
    }
}

3. 消息顺序性

// 如果需要顺序消费,用同一个队列,一个消费者
@Bean
public Consumer<Message<OrderEvent>> orderInput() {
    return message -> {
        // 处理消息
        // 保证同一个订单ID的消息顺序处理
    };
}
// 或者用RabbitMQ的单一消费者
@Bean
public MessageChannel orderInput() {
    return new DirectChannel();
}

4. 监控告警

@Component
@Slf4j
public class MessageMonitor {
    @EventListener
    public void handleMessageEvent(MessageEvent event) {
        // 记录消息处理日志
        log.info("消息事件:{}", event);
        // 监控指标
        monitorMessageCount(event.getDestination());
        // 错误告警
        if (event.isError()) {
            sendAlert("消息处理失败:" + event.getErrorMessage());
        }
    }
    private void monitorMessageCount(String queueName) {
        // 记录到监控系统
        // Prometheus, InfluxDB等
    }
}

十、常见问题

1. 消息丢失怎么办?

解决方案:

  1. 生产者确认模式(publisher confirm)
  2. 消息持久化(delivery mode = persistent)
  3. 消费者手动确认(acknowledge-mode = manual)
  4. 死信队列监控

2. 消息重复消费怎么办?

解决方案:

  1. 消息加唯一ID
  2. 消费者做幂等处理
  3. 用Redis记录已处理消息

3. 消息堆积怎么办?

解决方案:

  1. 增加消费者数量
  2. 批量消费
  3. 监控队列长度,及时扩容
  4. 优化消费者处理速度

4. 顺序消息怎么保证?

解决方案:

  1. 单个队列,单个消费者
  2. 按业务ID哈希到不同队列
  3. 消费者内部排队处理

5. 如何测试?

@SpringBootTest
@RunWith(SpringRunner.class)
public class MessageTest {
    @Autowired
    private StreamBridge streamBridge;
    @Test
    public void testSendMessage() {
        // 发送测试消息
        Map<String, Object> event = new HashMap<>();
        event.put("test", "value");
        boolean sent = streamBridge.send("testOutput", event);
        assertTrue(sent);
    }
    @Test
    public void testConsumeMessage() throws InterruptedException {
        // 等待消费者处理
        Thread.sleep(5000);
        // 验证处理结果
        // ...
    }
}

十一、今儿个总结

零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目

资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。

学会了啥?

  1. ✅ 消息队列的作用(异步、解耦、削峰)
  2. ✅ RabbitMQ核心概念
  3. ✅ Spring Cloud Stream使用
  4. ✅ 生产者发消息(StreamBridge)
  5. ✅ 消费者收消息(@Bean Consumer)
  6. ✅ 消息确认、重试、死信队列
  7. ✅ 实际应用场景

关键点

  1. 异步处理:耗时操作放消息队列
  2. 削峰填谷:流量高峰用队列缓冲
  3. 解耦:服务之间不直接调用
  4. 可靠性:消息确认、持久化、重试
  5. 幂等性:防止重复消费

十二、明儿个学啥?

零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目

资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。

明天咱学分布式链路追踪!

  • 一个请求经过多个服务,出问题咋排查?
  • 哪个服务慢?哪个服务报错?
  • 用Sleuth + Zipkin追踪调用链
  • 跟破案一样,顺藤摸瓜找问题

明天咱当微服务侦探,追踪请求的每一步!🔍

© 版权声明

相关文章