1.消息队列到底是干嘛的?
消息队列(MQ,Message Queue)就是一个中间传话的 “快递柜” 。
系统 A 想给 系统 B 发数据
不直接打电话,而是把数据丢进快递柜
系统 B 有空了自己去取
系统 A 不用等系统 B,也不怕 B 挂了
这个 “快递柜” 就是 MQ。
常见的:
Kafka:大数据、高吞吐、日志、事件流
RabbitMQ:业务系统、可靠投递、延迟队列
RocketMQ:阿里系,电商高并发
Pulsar:新一代云原生消息队列
2.MQ 基础
- Producer(生产者)
发消息的一方(订单服务)
- Consumer(消费者)
收消息的一方(库存服务)
- Topic / Queue
- Topic:发布订阅,所有人都能收到
- Queue:点对点,一个人收到就没了
- Consumer Group(消费组)
同一组里的多个实例,消息只分给其中一个用来做负载均衡,防止重复消费。
3.SpringCloudStream基础
- 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 之间的翻译官 + 司机。
- Binding(绑定)
一句话:应用和 MQ 之间的 “管道”
Binding 就是一条单向通道,分两种:
Input Binding(输入通道)
MQ → 你的应用你在这里消费消息。
Output Binding(输出通道)
你的应用 → MQ你在这里发送消息。
生活类比
- Input Binding:家门口的收件口
- Output Binding:家门口的寄件口
你不用管快递员怎么运输,你只用:
- 从收件口拿东西
- 往寄件口扔东西
Binding 就是这两个口。
- 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,格式都一样。
- Destination(目标)
一句话:快递柜编号 / 房间号
不同 MQ 名字不一样,但本质都是:消息要发去哪个 “地方”
- Kafka 里叫 Topic
- RabbitMQ 里叫 Exchange
- RocketMQ 里也叫 Topic
Destination 就是统一名称:你配置里写:
destination: order-topic
Spring Cloud Stream 会自动翻译成:
- Kafka → Topic: order-topic
- RabbitMQ → Exchange: order-topic
你不用关心底层叫啥,统一叫 Destination。
完整流程:
你要做一个 “订单创建后通知库存” 的功能:
-
订单服务发消息
- 应用把消息丢进 Output Binding
- Binder 把消息发到 Destination(order-topic)
-
MQ 存着消息
-
库存服务收消息
- Binder 从 Destination 拉消息
- 丢给 Input Binding
- 你的业务代码从 Input 拿到 Message
整个过程:
应用 → Output Binding → Binder → Destination(MQ) → Binder → Input Binding → 应用
你只写业务代码,连接、Topic、发送、消费,全由 Binder 搞定。
总结:
- Binder:
spring-cloud-stream-binder-rabbit(自动处理 RabbitMQ 连接) - Binding:
my-consumer-in-0、my-producer-out-0(收发通道) - Message:
String message(消息体) - Destination:
my-topic(消息发去的地方)
RabbitMQ 代码
- 创建连接
- 创建通道
- 声明队列
- 发消息
- 监听消息
- 依赖 pom.xml
xml
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.18.0</version>
</dependency>
- 生产者(发消息)
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);
}
}
}
- 消费者(收消息)
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
- 只写业务逻辑
- 依赖 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>
- 配置 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
- 代码(生产者 + 消费者)
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 只改配置,代码一行不动