MQ---Spring Cloud Stream

2 阅读5分钟

 

1.消息队列到底是干嘛的?

消息队列(MQ,Message Queue)就是一个中间传话的 “快递柜”

        系统 A 想给 系统 B 发数据

        不直接打电话,而是把数据丢进快递柜

        系统 B 有空了自己去取

        系统 A 不用等系统 B,也不怕 B 挂了

这个 “快递柜” 就是 MQ

常见的:

        Kafka:大数据、高吞吐、日志、事件流

        RabbitMQ:业务系统、可靠投递、延迟队列

        RocketMQ:阿里系,电商高并发

        Pulsar:新一代云原生消息队列

2.MQ 基础

  1. Producer(生产者)

发消息的一方(订单服务)

  1. Consumer(消费者)

收消息的一方(库存服务)

  1. Topic / Queue
  • Topic:发布订阅,所有人都能收到
  • Queue:点对点,一个人收到就没了
  1. Consumer Group(消费组)

同一组里的多个实例,消息只分给其中一个用来做负载均衡,防止重复消费。

3.SpringCloudStream基础

  1. Binder(绑定器)

一句话:MQ 的驱动包 + 适配器

你可以把它理解成:

  • JDBC 驱动 → 连接 MySQL/Oracle
  • Binder → 连接 Kafka/RabbitMQ/RocketMQ

角色

Spring Cloud Stream 本身不认识任何 MQ,它只定义一套标准接口,然后让各个 MQ 厂商自己写实现,这个实现就是 Binder

作用

  • 帮你建立到 MQ 的连接
  • 帮你创建 Topic/Queue
  • 帮你发消息、拉消息
  • 帮你处理重试、死信、分区

实现

  • Kafka:spring-cloud-stream-binder-kafka
  • RabbitMQ:spring-cloud-stream-binder-rabbit
  • RocketMQ:阿里自己写的 rocketmq-spring-boot-starter + binder

一句话总结: Binder = Spring Cloud Stream 与 MQ 之间的翻译官 + 司机。


  1. Binding(绑定)

一句话:应用和 MQ 之间的 “管道”

Binding 就是一条单向通道,分两种:

Input Binding(输入通道)

MQ → 你的应用你在这里消费消息

Output Binding(输出通道)

你的应用 → MQ你在这里发送消息

生活类比

  • Input Binding:家门口的收件口
  • Output Binding:家门口的寄件口

你不用管快递员怎么运输,你只用:

  • 从收件口拿东西
  • 往寄件口扔东西

Binding 就是这两个口。


  1. Message(消息)

一句话:快递包裹本身

标准结构:

  • Payload:包裹里的东西(业务数据)
  • Headers:快递单(标签、key-value 信息)

例如:

Headers:
  timestamp: 1712345678
  contentType: application/json
  traceId: a1b2c3

Payload:
  {
    "orderId": 123,
    "userId": 456,
    "amount": 99
  }

Spring Cloud Stream 统一封装成 Message<T>,不管底层是 Kafka/Rabbit,格式都一样。


  1. Destination(目标)

一句话:快递柜编号 / 房间号

不同 MQ 名字不一样,但本质都是:消息要发去哪个 “地方”

  • Kafka 里叫 Topic
  • RabbitMQ 里叫 Exchange
  • RocketMQ 里也叫 Topic

Destination 就是统一名称:你配置里写:

destination: order-topic

Spring Cloud Stream 会自动翻译成:

  • Kafka → Topic: order-topic
  • RabbitMQ → Exchange: order-topic

你不用关心底层叫啥,统一叫 Destination。

完整流程:

你要做一个 “订单创建后通知库存” 的功能:

  1. 订单服务发消息

    • 应用把消息丢进 Output Binding
    • Binder 把消息发到 Destination(order-topic)
  2. MQ 存着消息

  3. 库存服务收消息

    • BinderDestination 拉消息
    • 丢给 Input Binding
    • 你的业务代码从 Input 拿到 Message

整个过程:

应用 → Output Binding → Binder → Destination(MQ) → Binder → Input Binding → 应用

你只写业务代码,连接、Topic、发送、消费,全由 Binder 搞定。

总结:

  • Binderspring-cloud-stream-binder-rabbit(自动处理 RabbitMQ 连接)
  • Bindingmy-consumer-in-0my-producer-out-0(收发通道)
  • MessageString message(消息体)
  • Destinationmy-topic(消息发去的地方)

 RabbitMQ 代码

  • 创建连接
  • 创建通道
  • 声明队列
  • 发消息
  • 监听消息
  1. 依赖 pom.xml

xml

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.18.0</version>
</dependency>

  1. 生产者(发消息)
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class RabbitProducer {
    private final static String QUEUE_NAME = "test-queue";

    public static void main(String[] args) throws Exception {
        // 1. 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setUsername("guest");
        factory.setPassword("guest");

        // 2. 创建连接 + 通道
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            // 3. 声明队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "我是原生 RabbitMQ 消息";

            // 4. 发送消息
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println("发送消息:" + message);
        }
    }
}

  1. 消费者(收消息)
import com.rabbitmq.client.*;

public class RabbitConsumer {
    private final static String QUEUE_NAME = "test-queue";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println("等待消息...");

        // 回调:收到消息就触发
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody());
            System.out.println("收到消息:" + message);
        };

        // 持续监听
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
    }
}

Spring Cloud Stream + RabbitMQ代码

  • 没有连接
  • 没有通道
  • 没有队列声明
  • 没有 RabbitMQ 任何 API
  • 只写业务逻辑
  1. 依赖 pom.xml
<!-- Spring Boot -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
</parent>

<dependencies>
    <!-- Spring Cloud Stream + RabbitMQ Binder -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
    </dependency>
</dependencies>

<!-- Spring Cloud 版本管理 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2023.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

  1. 配置 application.yml
spring:
  cloud:
    stream:
      # Binder:自动对接 RabbitMQ
      rabbit:
        binder:
          hosts: localhost
          username: guest
          password: guest

      # 绑定消息通道
      bindings:
        # 消费者通道
        my-consumer-in-0:
          destination: my-topic
        # 生产者通道
        my-producer-out-0:
          destination: my-topic

  1. 代码(生产者 + 消费者)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.function.Consumer;
import java.util.function.Supplier;

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

    // ==================== 消费者:收消息 ====================
    @Bean
    public Consumer<String> myConsumer() {
        return message -> {
            System.out.println("【SCS 消费者收到】" + message);
        };
    }

    // ==================== 生产者:发消息 ====================
    @Bean
    public Supplier<String> myProducer() {
        return () -> "我是 SCS 自动发送的消息";
    }
}

启动直接运行

对比

原生 RabbitMQ

  • 写连接、通道、队列、异常处理
  • 代码绑定 RabbitMQ
  • 换中间件 = 重写

Spring Cloud Stream

  • 只写业务逻辑
  • 完全看不到中间件 API
  • Binder 帮你处理所有底层连接、收发、配置
  • 换 Kafka 只改配置,代码一行不动