目录
-
[版本选择与 Maven 依赖](#版本选择与 Maven 依赖)
-
-
4.1 函数式编程模型(推荐)
-
4.3 [StreamBridge --- 动态发送](#StreamBridge — 动态发送)
-
4.4 [轮询消费者(Pollable Consumer)](#轮询消费者(Pollable Consumer))
-
-
[Binder 实战](#Binder 实战)
-
6.1 [Kafka Binder](#Kafka Binder)
-
6.2 [RabbitMQ Binder](#RabbitMQ Binder)
-
6.3 [多 Binder 共存](#多 Binder 共存)
-
-
[自定义 Binder Starter 开发](#自定义 Binder Starter 开发)
1. 概述
Spring Cloud Stream 是一个用于构建消息驱动微服务的框架,它基于 Spring Boot 和 Spring Integration,提供了一套中间件无关的编程模型。
核心价值:
-
Binder 抽象:应用代码与消息中间件(Kafka / RabbitMQ / Pulsar 等)解耦
-
函数式编程模型 :
java.util.function风格的Supplier/Function/Consumer -
声明式配置 :通过
application.yml控制绑定、分区、消费者组、并发 -
开箱即用:自动检测 classpath 上的 Binder,自动创建 Topic / Queue / Exchange
架构图(简化):
┌─────────────────────────────────────────────────┐
│ Spring Cloud Stream App │
│ ┌──────────┐ ┌────────────┐ ┌──────────┐ │
│ │ Supplier │───▶│ Function │───▶│ Consumer │ │
│ └────┬─────┘ └─────┬──────┘ └────┬─────┘ │
│ │ │ │ │
│ ┌────▼────────────────▼────────────────▼─────┐ │
│ │ Binder (Kafka) │ │
│ └────────────────────┬───────────────────────┘ │
└───────────────────────┼──────────────────────────┘
│
┌────────▼────────┐
│ Kafka Broker │
└─────────────────┘
2. 版本选择与 Maven 依赖
JDK 8 兼容版本矩阵
| Spring Cloud Stream | Spring Boot | Spring Cloud | JDK |
|---|---|---|---|
| 3.2.x(推荐) | 2.7.x | 2021.0.x | 8 |
| 3.1.x | 2.6.x | 2021.0.x | 8 |
| 3.0.x | 2.4.x-2.5.x | 2020.0.x | 8 |
4.x+ 需要 JDK 17 和 Spring Boot 3.x,不适用于 JDK 8。
Maven 依赖
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.7.18</spring-boot.version>
<spring-cloud.version>2021.0.9</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Cloud Stream 核心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<!-- Kafka Binder -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
<!-- 或 RabbitMQ Binder -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<!-- 测试 Binder(单元测试用) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<type>test-jar</type>
<scope>test</scope>
<classifier>test-binder</classifier>
</dependency>
<!-- Spring Boot Starter Web(REST API 触发消息时用) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3. 核心概念
| 概念 | 说明 | 示例 |
|---|---|---|
| Binder | 中间件适配层,负责与具体消息系统交互 | kafka、rabbit |
| Binding | 应用输入/输出与中间件 Destination 之间的桥梁 | input、output、process-in、process-out |
| Destination | 中间件中的物理地址 | Kafka Topic、RabbitMQ Exchange/Queue |
| Consumer Group | 消费者组,同组内竞争消费 | order-service-group |
| Partition | 分区,保证同一 Key 的消息到达同一实例 | partitionKeyExpression |
| Supplier | 消息生产者(函数式模型) | Supplier<Flux<Message<T>>> |
| Function | 消息处理器,接收并返回 | Function<String, String> |
| Consumer | 消息消费者(函数式模型) | Consumer<String> |
函数式绑定命名规则
Supplier<O> → <functionName>-out-0
Function<I,O> → <functionName>-in-0 / <functionName>-out-0
Consumer<I> → <functionName>-in-0
例如 @Bean public Function<String, String> uppercase() 对应绑定名 uppercase-in-0 和 uppercase-out-0。
4. 编程模型
4.1 函数式编程模型(推荐)
从 Spring Cloud Stream 3.0 开始,函数式编程模型 成为首选。它基于 java.util.function 包,无需任何框架注解即可定义消息处理器。
Supplier --- 消息生产者
@SpringBootApplication
public class SourceApplication {
public static void main(String[] args) {
SpringApplication.run(SourceApplication.class, args);
}
// Supplier 每秒生成一条消息
@Bean
public Supplier<String> timeSupplier() {
return () -> {
String time = LocalDateTime.now().toString();
System.out.println("Sending: " + time);
return time;
};
}
}
application.yml:
spring:
cloud:
stream:
function:
definition: timeSupplier # 声明要绑定的函数
bindings:
timeSupplier-out-0: # Supplier 的输出绑定
destination: time-topic # Kafka Topic / RabbitMQ Exchange
kafka:
binder:
brokers: localhost:9092
Function --- 消息处理器
@SpringBootApplication
public class ProcessorApplication {
public static void main(String[] args) {
SpringApplication.run(ProcessorApplication.class, args);
}
@Bean
public Function<String, String> uppercase() {
return String::toUpperCase;
}
@Bean
public Function<Order, OrderResult> orderProcessor() {
return order -> {
log.info("Processing order: {}", order.getId());
return new OrderResult(order.getId(), "PROCESSED");
};
}
}
application.yml:
spring:
cloud:
stream:
function:
definition: uppercase;orderProcessor # 多个函数用 ; 分隔
bindings:
uppercase-in-0:
destination: text-topic
group: processor-group
uppercase-out-0:
destination: upper-text-topic
orderProcessor-in-0:
destination: order-topic
group: order-group
orderProcessor-out-0:
destination: order-result-topic
Consumer --- 消息消费者
@SpringBootApplication
public class SinkApplication {
public static void main(String[] args) {
SpringApplication.run(SinkApplication.class, args);
}
@Bean
public Consumer<String> logSink() {
return message -> log.info("Received: {}", message);
}
@Bean
public Consumer<OrderResult> orderResultHandler(OrderService orderService) {
return orderResult -> {
log.info("Order processed: {}", orderResult);
orderService.updateStatus(orderResult);
};
}
}
函数组合(Function Composition)
// 多个 Function 可以串联:uppercase → reverse
@Bean
public Function<String, String> uppercase() {
return String::toUpperCase;
}
@Bean
public Function<String, String> reverse() {
return s -> new StringBuilder(s).reverse().toString();
}
spring:
cloud:
stream:
function:
definition: uppercase|reverse # | 表示管道串联
bindings:
uppercase|reverse-in-0:
destination: text-topic
uppercase|reverse-out-0:
destination: reversed-topic
4.2 传统注解模型(@EnableBinding)
⚠️ 此模型在 3.x 中仍可用但已标记为 deprecated,4.x 中完全移除。仅建议在维护旧项目时使用。
// === 定义接口 ===
public interface OrderProcessor {
String INPUT = "order-input";
String OUTPUT = "order-output";
@Input(INPUT)
SubscribableChannel input();
@Output(OUTPUT)
MessageChannel output();
}
// === 使用 ===
@SpringBootApplication
@EnableBinding(OrderProcessor.class)
public class LegacyApp {
@StreamListener(OrderProcessor.INPUT)
@SendTo(OrderProcessor.OUTPUT)
public OrderResult process(Order order) {
return new OrderResult(order.getId(), "DONE");
}
}
spring:
cloud:
stream:
bindings:
order-input:
destination: order-topic
group: order-group
order-output:
destination: order-result-topic
从注解模型迁移到函数式的对照表:
| 注解模型 | 函数式模型 |
|---|---|
@EnableBinding(Source.class) + @InboundChannelAdapter |
Supplier<O> |
@EnableBinding(Processor.class) + @StreamListener + @SendTo |
Function<I, O> |
@EnableBinding(Sink.class) + @StreamListener |
Consumer<I> |
4.3 StreamBridge --- 动态发送
当需要在非函数式上下文(如 REST Controller)中发送消息时,使用 StreamBridge:
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private StreamBridge streamBridge;
@PostMapping
public ResponseEntity<String> createOrder(@RequestBody Order order) {
// 动态发送到指定 binding
boolean sent = streamBridge.send("orderSupplier-out-0", order);
if (sent) {
return ResponseEntity.accepted().body("Order submitted: " + order.getId());
}
return ResponseEntity.status(500).body("Failed to send order");
}
// 发送到自定义 destination
@PostMapping("/urgent")
public ResponseEntity<String> urgentOrder(@RequestBody Order order) {
streamBridge.send("urgent-orders-topic", order,
MimeTypeUtils.APPLICATION_JSON);
return ResponseEntity.ok("Urgent order queued");
}
}
4.4 轮询消费者(Pollable Consumer)
适用于需要按需拉取而非被动推送的场景:
@Bean
public Consumer<Flux<InputDestination>> pollingConsumer() {
// 注意:Flux 是 Project Reactor 的类,JDK 8 需额外引入
return flux -> flux.subscribe(data -> log.info("Received: {}", data));
}
配置轮询模式:
spring:
cloud:
stream:
pollable-source: myPoller # 声明轮询源名称
bindings:
myPoller-in-0:
destination: polled-topic
group: poll-group
5. 配置体系
5.1 基础配置
spring:
application:
name: order-service
cloud:
stream:
# 声明要激活的函数(多个用 ; 分隔)
function:
definition: orderSupplier;orderProcessor;orderSink
# 默认 Binder
default-binder: kafka
5.2 Binder 配置
spring:
cloud:
stream:
kafka:
binder:
brokers: kafka1:9092,kafka2:9092,kafka3:9092
defaultBrokerPort: 9092
zkNodes: zk1:2181,zk2:2181 # 3.x 中仍支持 ZooKeeper
configuration:
# 生产者配置
acks: all
retries: 3
batch.size: 16384
linger.ms: 10
compression.type: snappy
# 消费者配置
enable.auto.commit: false
auto.offset.reset: earliest
max.poll.records: 500
# 生产者属性
producerProperties:
max:
request:
size: 1048576
# 消费者属性
consumerProperties:
heartbeat:
interval:
ms: 3000
5.3 Binding 配置
spring:
cloud:
stream:
bindings:
# ========== 生产者绑定 ==========
orderSupplier-out-0:
destination: order-topic # 目标 Topic/Exchange
content-type: application/json # 消息内容类型
producer:
partition-key-expression: payload.id # 分区键表达式
partition-count: 3 # 分区数
use-native-encoding: true # 跳过框架序列化
# 发送超时
send-timeout: 5000
# 错误通道
error-channel-enabled: true
# ========== 消费者绑定 ==========
orderProcessor-in-0:
destination: order-topic
group: order-processing-group # 消费者组(关键配置!)
content-type: application/json
consumer:
concurrency: 3 # 并发消费者数
max-attempts: 3 # 最大重试次数
back-off-initial-interval: 1000 # 退避初始间隔(ms)
back-off-max-interval: 10000 # 退避最大间隔
back-off-multiplier: 2.0 # 退避乘数
# 启用 DLQ(Kafka)
enable-dlq: true
dlq-name: order-topic-dlq
# 批量消费
batch-mode: false
# 手动确认
auto-commit-offset: true
5.4 消费者组
消费者组是实现负载均衡 和持久化订阅的核心机制:
# 实例 1 和实例 2 属于同一组,消息仅被其中一个消费
spring:
cloud:
stream:
bindings:
orderProcessor-in-0:
destination: order-topic
group: order-service-group # ← 关键!
消费者组行为:
| 配置 | 行为 |
|---|---|
有 group |
组内竞争消费(负载均衡)、持久化订阅(Broker 记住 offset) |
无 group |
匿名独立组(每个实例收到所有消息)、非持久化(重启后丢失) |
5.5 分区支持
分区确保相同 Key 的消息始终路由到同一实例:
spring:
cloud:
stream:
bindings:
orderSupplier-out-0:
destination: order-topic
producer:
partition-key-expression: payload.customerId # 按客户 ID 分区
partition-count: 6
orderProcessor-in-0:
destination: order-topic
group: order-service-group
consumer:
partitioned: true
# 实例索引(多实例部署时自动分配)
instance-index: 0
instance-count: 3
6. Binder 实战
6.1 Kafka Binder
基础配置
spring:
cloud:
stream:
kafka:
binder:
brokers: localhost:9092
# 自动创建 Topic
auto-create-topics: true
auto-add-partitions: true
min-partition-count: 3
# 事务支持
transaction:
transaction-id-prefix: tx-
producer:
transaction-id-prefix: ptx-
死信队列(DLQ)
spring:
cloud:
stream:
kafka:
bindings:
orderProcessor-in-0:
consumer:
enable-dlq: true
dlq-name: order-topic-dlq
dlq-partitions: 3
# 死信处理后的重试
auto-commit-on-error: true
手动确认
@Component
public class ManualAckConsumer {
@Bean
public Consumer<Message<Order>> manualOrderConsumer() {
return message -> {
Order order = message.getPayload();
Acknowledgment ack = message.getHeaders()
.get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class);
try {
process(order);
ack.acknowledge();
} catch (Exception e) {
ack.nack(Duration.ofSeconds(5)); // 5s 后重试
}
};
}
}
spring:
cloud:
stream:
kafka:
bindings:
manualOrderConsumer-in-0:
consumer:
auto-commit-offset: false
enable-dlq: true
批量消费
@Bean
public Consumer<List<Message<Order>>> batchOrderConsumer() {
return messages -> {
log.info("Received batch of {} orders", messages.size());
messages.forEach(msg -> process(msg.getPayload()));
};
}
spring:
cloud:
stream:
kafka:
bindings:
batchOrderConsumer-in-0:
consumer:
batch-mode: true
bindings:
batchOrderConsumer-in-0:
content-type: application/json
consumer:
batch-mode: true
6.2 RabbitMQ Binder
spring:
cloud:
stream:
rabbit:
binder:
addresses: localhost:5672
username: guest
password: guest
virtual-host: /
bindings:
orderProcessor-in-0:
destination: order-exchange # RabbitMQ Exchange 名称
group: order-service-group # 自动创建 Queue
consumer:
auto-bind-dlq: true # 自动创建 DLQ
dead-letter-queue-name: order-dlq
requeue-rejected: false # 拒绝时不重新入队
max-concurrency: 10
prefetch: 50
ttl: 60000 # 消息 TTL
6.3 多 Binder 共存
spring:
cloud:
stream:
default-binder: kafka
binders:
kafka:
type: kafka
environment:
spring:
cloud:
stream:
kafka:
binder:
brokers: kafka:9092
rabbit:
type: rabbit
environment:
spring:
cloud:
stream:
rabbit:
binder:
addresses: rabbitmq:5672
bindings:
orderSupplier-out-0:
destination: order-topic
binder: kafka # 指定使用 Kafka
notificationSupplier-out-0:
destination: notify-exchange
binder: rabbit # 指定使用 RabbitMQ
7. 消息转换与序列化
默认行为
Spring Cloud Stream 自动根据 content-type 头选择 MessageConverter:
| Content-Type | 转换器 |
|---|---|
application/json |
MappingJackson2MessageConverter |
application/x-java-object |
原生 Java 序列化 |
text/plain |
StringMessageConverter |
application/octet-stream |
ByteArrayMessageConverter |
配置 JSON 序列化
spring:
cloud:
stream:
bindings:
orderSupplier-out-0:
content-type: application/json
自定义 MessageConverter
@Configuration
public class CustomConverterConfig {
@Bean
@StreamMessageConverter
public MessageConverter customMessageConverter() {
// 自定义 Protobuf 转换器
return new ProtobufMessageConverter();
}
}
// ProtobufMessageConverter 示例
public class ProtobufMessageConverter extends AbstractMessageConverter {
public ProtobufMessageConverter() {
super(new MimeType("application", "x-protobuf"));
}
@Override
protected boolean supports(Class<?> clazz) {
return MessageLite.class.isAssignableFrom(clazz);
}
@Override
protected Object convertFromInternal(
Message<?> message, Class<?> targetClass, Object conversionHint) {
// 反序列化
return null; // 实现略
}
@Override
protected Object convertToInternal(
Object payload, MessageHeaders headers, Object conversionHint) {
// 序列化
return ((MessageLite) payload).toByteArray();
}
}
8. 错误处理与死信队列
全局错误处理
@Configuration
public class ErrorHandlerConfig {
// 全局错误通道 — 捕获所有绑定级错误
@ServiceActivator(inputChannel = "errorChannel")
public void handleError(ErrorMessage errorMessage) {
Throwable payload = errorMessage.getPayload();
Message<?> failedMessage = errorMessage.getOriginalMessage();
log.error("Message processing failed: {}", payload.getMessage(), payload);
// 存入数据库、发送告警等
}
}
绑定级别错误通道
// 为特定 binding 定义错误处理器
@ServiceActivator(inputChannel = "order-topic.order-service-group.errors")
public void handleOrderError(ErrorMessage errorMessage) {
log.error("Order failed: {}", errorMessage.getOriginalMessage());
}
spring:
cloud:
stream:
kafka:
bindings:
orderProcessor-in-0:
consumer:
enable-dlq: true
dlq-name: order-topic-dlq
bindings:
orderProcessor-in-0:
consumer:
max-attempts: 5
back-off-initial-interval: 1000
back-off-max-interval: 30000
back-off-multiplier: 2.0
自定义 RetryTemplate
@Configuration
public class RetryConfig {
@Bean
@StreamRetryTemplate
public RetryTemplate customRetryTemplate() {
RetryTemplate template = new RetryTemplate();
// 指数退避:1s → 2s → 4s → 8s → 16s
ExponentialBackOffPolicy backOff = new ExponentialBackOffPolicy();
backOff.setInitialInterval(1000);
backOff.setMultiplier(2.0);
backOff.setMaxInterval(30000);
// 简单重试策略
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3);
template.setBackOffPolicy(backOff);
template.setRetryPolicy(retryPolicy);
return template;
}
}
分类异常处理
@Component
public class OrderProcessor {
// 可重试异常:临时故障,允许重试
@Bean
public Function<Order, OrderResult> orderProcessor() {
return order -> {
try {
return processOrder(order);
} catch (TimeoutException | ConnectException e) {
// 网络/TCP 异常 — 自动重试
throw new RuntimeException(e);
} catch (ValidationException e) {
// 业务校验异常 — 不应重试
throw new ImmediateAcknowledgeAmqpException(e);
}
};
}
}
9. 测试支持
使用 Test Binder
@SpringBootTest
@Import(TestChannelBinderConfiguration.class) // 导入 Test Binder
class OrderProcessorTest {
@Autowired
private InputDestination input; // 模拟输入
@Autowired
private OutputDestination output; // 捕获输出
@Test
void testOrderProcessing() {
// 发送测试消息到 orderProcessor-in-0
Order order = new Order("O-001", 100);
Message<Order> message = MessageBuilder
.withPayload(order)
.setHeader("contentType", "application/json")
.build();
input.send(message, "order-topic");
// 验证输出 — 从 orderProcessor-out-0 接收
Message<byte[]> result = output.receive(5000, "order-result-topic");
assertNotNull(result);
OrderResult orderResult = parseJson(result.getPayload(), OrderResult.class);
assertEquals("O-001", orderResult.getOrderId());
assertEquals("PROCESSED", orderResult.getStatus());
}
}
轮询消费者测试
@SpringBootTest
@Import(TestChannelBinderConfiguration.class)
class PollingConsumerTest {
@Test
void testPollingConsumer(PollableMessageSource source,
OutputDestination output) {
// 发送测试消息
// 轮询并验证
boolean received = source.poll(message -> {
assertEquals("test-data", message.getPayload());
});
assertTrue(received);
}
}
完整测试配置
# src/test/resources/application.yml
spring:
cloud:
stream:
default-binder: test # 使用 Test Binder
bindings:
orderProcessor-in-0:
destination: order-topic
group: test-group
orderProcessor-out-0:
destination: order-result-topic
10. 完整示例
10.1 订单处理流水线
[REST API] → [Supplier] → [order-topic] → [Function: validate] → [Function: enrich]
→ [Consumer: persist] + [Consumer: notify]
项目依赖(pom.xml 关键部分):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
</dependencies>
OrderController.java --- REST 入口:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private StreamBridge streamBridge;
@PostMapping
public ResponseEntity<Map<String, String>> createOrder(@RequestBody Order order) {
order.setOrderId(UUID.randomUUID().toString());
order.setCreateTime(new Date());
boolean sent = streamBridge.send("orderProducer-out-0",
MessageBuilder.withPayload(order)
.setHeader("event-type", "ORDER_CREATED")
.build());
if (sent) {
return ResponseEntity.accepted()
.body(Collections.singletonMap("orderId", order.getOrderId()));
}
return ResponseEntity.status(500)
.body(Collections.singletonMap("error", "send failed"));
}
}
OrderPipeline.java --- 核心流水线:
@SpringBootApplication
@Slf4j
public class OrderPipeline {
public static void main(String[] args) {
SpringApplication.run(OrderPipeline.class, args);
}
// ===== 生产者 =====
@Bean
public Supplier<Message<Order>> orderProducer() {
// 实际由 StreamBridge 发送,此处声明绑定
return () -> null;
}
// ===== 第一步:校验 =====
@Bean
public Function<Message<Order>, Message<Order>> validateOrder() {
return message -> {
Order order = message.getPayload();
log.info("Validating order: {}", order.getOrderId());
if (order.getAmount() == null || order.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new OrderValidationException("Invalid amount: " + order.getAmount());
}
order.setStatus("VALIDATED");
return MessageBuilder.withPayload(order)
.copyHeaders(message.getHeaders())
.build();
};
}
// ===== 第二步:丰富订单信息 =====
@Bean
public Function<Message<Order>, Message<Order>> enrichOrder(
CustomerService customerService) {
return message -> {
Order order = message.getPayload();
log.info("Enriching order: {}", order.getOrderId());
Customer customer = customerService.findById(order.getCustomerId());
order.setCustomerName(customer.getName());
order.setCustomerLevel(customer.getLevel());
order.setStatus("ENRICHED");
return MessageBuilder.withPayload(order)
.copyHeaders(message.getHeaders())
.build();
};
}
// ===== 第三步:持久化 =====
@Bean
public Consumer<Message<Order>> persistOrder(OrderRepository repository) {
return message -> {
Order order = message.getPayload();
log.info("Persisting order: {}", order.getOrderId());
repository.save(order);
};
}
// ===== 第四步:通知 =====
@Bean
public Consumer<Message<Order>> notifyOrder(NotificationService notificationService) {
return message -> {
Order order = message.getPayload();
String eventType = message.getHeaders().get("event-type", String.class);
log.info("Sending notification for order {} [{}]", order.getOrderId(), eventType);
notificationService.send(order);
};
}
}
application.yml:
spring:
application:
name: order-pipeline
cloud:
stream:
function:
definition: orderProducer;validateOrder;enrichOrder;persistOrder;notifyOrder
bindings:
orderProducer-out-0:
destination: order-topic
content-type: application/json
producer:
partition-key-expression: payload.orderId
partition-count: 3
validateOrder-in-0:
destination: order-topic
group: validate-group
content-type: application/json
validateOrder-out-0:
destination: order-validated-topic
content-type: application/json
enrichOrder-in-0:
destination: order-validated-topic
group: enrich-group
content-type: application/json
enrichOrder-out-0:
destination: order-enriched-topic
content-type: application/json
persistOrder-in-0:
destination: order-enriched-topic
group: persist-group
content-type: application/json
consumer:
max-attempts: 3
back-off-initial-interval: 1000
notifyOrder-in-0:
destination: order-enriched-topic
group: notify-group
content-type: application/json
kafka:
binder:
brokers: localhost:9092
auto-create-topics: true
auto-add-partitions: true
bindings:
validateOrder-in-0:
consumer:
enable-dlq: true
dlq-name: order-topic-dlq
enrichOrder-in-0:
consumer:
enable-dlq: true
dlq-name: order-validated-dlq
10.2 事件驱动微服务 --- 用户积分系统
场景:下单 → 扣减库存 → 计算积分 → 发送积分通知
@SpringBootApplication
public class PointsService {
public static void main(String[] args) {
SpringApplication.run(PointsService.class, args);
}
// 监听订单创建事件,计算积分
@Bean
public Function<OrderEvent, PointsEvent> calculatePoints(PointsRule rule) {
return orderEvent -> {
int points = rule.calculate(orderEvent.getAmount());
log.info("Order {} earned {} points", orderEvent.getOrderId(), points);
return PointsEvent.builder()
.userId(orderEvent.getUserId())
.orderId(orderEvent.getOrderId())
.points(points)
.timestamp(Instant.now())
.build();
};
}
// 监听积分事件,更新用户总积分
@Bean
public Consumer<PointsEvent> updateTotalPoints(UserPointsRepository repo) {
return event -> {
repo.addPoints(event.getUserId(), event.getPoints());
log.info("Updated points for user {}: +{}", event.getUserId(), event.getPoints());
};
}
}
spring:
cloud:
stream:
function:
definition: calculatePoints;updateTotalPoints
bindings:
calculatePoints-in-0:
destination: order-enriched-topic
group: points-calc-group
calculatePoints-out-0:
destination: points-awarded-topic
updateTotalPoints-in-0:
destination: points-awarded-topic
group: points-update-group
kafka:
binder:
brokers: localhost:9092
bindings:
calculatePoints-in-0:
consumer:
enable-dlq: true
dlq-name: points-calc-dlq
11. 自定义 Binder Starter 开发
基于官方文档 overview-custom-binder-impl.html 中的 FileMessageBinder 示例,开发一个完整可发布的 Binder Starter。
项目结构
custom-binder-starter/
├── pom.xml
└── src/main/java/com/example/binder/
├── FileMessageBinder.java # Binder 实现
├── FileMessageBinderProvisioner.java # ProvisioningProvider
├── FileMessageBinderConfiguration.java # 自动配置
├── FileMessageProducer.java # 消费者端点
└── org/springframework/cloud/stream/binder/
└── BinderHeaderMapper.java
└── src/main/resources/
└── META-INF/
├── spring.factories # Spring Boot 自动配置
└── spring.binders # Binder 注册
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring-cloud-stream-binder-file</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
<spring-cloud-stream.version>3.2.10</spring-cloud-stream.version>
</properties>
<dependencies>
<!-- Binder 核心 API -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<version>${spring-cloud-stream.version}</version>
</dependency>
<!-- Spring Boot 自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- 可选:配置元数据生成 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
FileMessageBinderProvisioner.java --- 资源供给器
package com.example.binder;
import org.springframework.cloud.stream.binder.*;
import org.springframework.cloud.stream.provisioning.ProvisioningProvider;
/**
* 负责创建/销毁 ProducerDestination 和 ConsumerDestination。
* 对于文件 Binder,"Destination" 就是文件路径。
*/
public class FileMessageBinderProvisioner
implements ProvisioningProvider<ExtendedConsumerProperties<Object>,
ExtendedProducerProperties<Object>> {
@Override
public ProducerDestination provisionProducerDestination(
String name,
ExtendedProducerProperties<Object> properties) {
return new FileProducerDestination(name);
}
@Override
public ConsumerDestination provisionConsumerDestination(
String name,
String group,
ExtendedConsumerProperties<Object> properties) {
return new FileConsumerDestination(name, group);
}
// Producer Destination 实现
static class FileProducerDestination implements ProducerDestination {
private final String fileName;
FileProducerDestination(String fileName) {
this.fileName = fileName;
}
@Override
public String getName() {
return fileName + ".data";
}
@Override
public String getNameForPartition(int partition) {
return fileName + "-" + partition + ".data";
}
}
// Consumer Destination 实现
static class FileConsumerDestination implements ConsumerDestination {
private final String fileName;
private final String group;
FileConsumerDestination(String fileName, String group) {
this.fileName = fileName;
this.group = group;
}
@Override
public String getName() {
return fileName + (group != null ? "-" + group : "") + ".data";
}
}
}
FileMessageProducer.java --- 消费者端点(从文件读取)
package com.example.binder;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import java.io.*;
import java.nio.file.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class FileMessageProducer extends MessageProducerSupport {
private final String filePath;
private long lastPosition = 0;
private ScheduledExecutorService executor;
public FileMessageProducer(ConsumerDestination destination) {
this.filePath = destination.getName();
}
@Override
protected void doStart() {
executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleWithFixedDelay(this::pollFile, 1, 1, TimeUnit.SECONDS);
}
private void pollFile() {
try {
Path path = Paths.get(filePath);
if (!Files.exists(path)) {
return;
}
long fileSize = Files.size(path);
if (fileSize > lastPosition) {
try (RandomAccessFile raf = new RandomAccessFile(filePath, "r")) {
raf.seek(lastPosition);
String line;
while ((line = raf.readLine()) != null) {
if (!line.isEmpty()) {
Message<String> message = new GenericMessage<>(line);
sendMessage(message);
}
}
lastPosition = raf.getFilePointer();
}
}
} catch (IOException e) {
logger.error("Error polling file: " + filePath, e);
}
}
@Override
protected void doStop() {
if (executor != null) {
executor.shutdown();
}
}
}
FileMessageBinder.java --- Binder 核心实现
package com.example.binder;
import org.springframework.cloud.stream.binder.*;
import org.springframework.cloud.stream.provisioning.ConsumerDestination;
import org.springframework.cloud.stream.provisioning.ProducerDestination;
import org.springframework.integration.core.MessageProducer;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class FileMessageBinder extends
AbstractMessageBinder<ExtendedConsumerProperties<Object>,
ExtendedProducerProperties<Object>,
FileMessageBinderProvisioner> {
public FileMessageBinder(
String[] headersToEmbed,
FileMessageBinderProvisioner provisioner) {
super(headersToEmbed, provisioner);
}
/**
* 创建生产者消息处理器 — 将消息写入文件
*/
@Override
protected MessageHandler createProducerMessageHandler(
ProducerDestination destination,
ExtendedProducerProperties<Object> producerProperties,
MessageChannel errorChannel) {
return message -> {
String fileName = destination.getName();
String payload = new String((byte[]) message.getPayload()) + "\n";
try {
Files.write(Paths.get(fileName), payload.getBytes(),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
logger.info("Message written to file: " + fileName);
} catch (IOException e) {
// 发送错误到错误通道
if (errorChannel != null) {
errorChannel.send(
new org.springframework.messaging.support.ErrorMessage(e));
}
throw new RuntimeException("Failed to write to file: " + fileName, e);
}
};
}
/**
* 创建消费者端点 — 从文件读取消息
*/
@Override
protected MessageProducer createConsumerEndpoint(
ConsumerDestination destination,
String group,
ExtendedConsumerProperties<Object> properties) {
return new FileMessageProducer(destination);
}
}
FileMessageBinderConfiguration.java --- 自动配置
package com.example.binder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FileMessageBinderConfiguration {
@Bean
@ConditionalOnMissingBean
public FileMessageBinderProvisioner fileMessageBinderProvisioner() {
return new FileMessageBinderProvisioner();
}
@Bean
@ConditionalOnMissingBean
public FileMessageBinder fileMessageBinder(
FileMessageBinderProvisioner provisioner) {
return new FileMessageBinder(null, provisioner);
}
}
META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.binder.FileMessageBinderConfiguration
META-INF/spring.binders
# binderName:\
# fully.qualified.ConfigurationClass
file:\
com.example.binder.FileMessageBinderConfiguration
使用自定义 Binder
1. 引入依赖:
<dependency>
<groupId>com.example</groupId>
<artifactId>spring-cloud-stream-binder-file</artifactId>
<version>1.0.0</version>
</dependency>
2. 配置应用:
spring:
cloud:
stream:
default-binder: file
function:
definition: fileWriter;fileReader
bindings:
fileWriter-out-0:
destination: output-data
fileReader-in-0:
destination: input-data
group: reader-group
3. 应用代码(无需改动):
@Bean
public Supplier<String> fileWriter() {
return () -> "Data line: " + UUID.randomUUID();
}
@Bean
public Consumer<String> fileReader() {
return line -> log.info("Read from file: {}", line);
}
12. 最佳实践清单
架构设计
-
✅ 优先使用函数式编程模型 (
Supplier/Function/Consumer),避免@EnableBinding -
✅ 合理拆分 Function :每个
Function只做一件事(单一职责) -
✅ 使用函数组合 (
|管道符)串联多个 Function,而非在一个函数中做所有事 -
✅ 区分有状态与无状态:状态通过外部存储(DB / Redis)管理,而非在 Function 内部
配置管理
-
✅ 始终为消费者配置
group:保证负载均衡和持久化订阅 -
✅ 生产环境设置
auto-offset-reset: latest:避免消费历史数据 -
✅ 配置
max-attempts+back-off:防止无限重试 -
✅ 启用 DLQ:重试耗尽的消息进入死信,便于排查
-
✅ 分区键选择高基数字段(如 userId、orderId),避免数据倾斜
Kafka 专项
-
✅ 合理配置
auto-create-topics: true:开发环境方便,生产环境建议手动创建 -
✅ 批量消费打开
batch-mode: true+ 设置max.poll.records提高吞吐 -
✅ 生产者配置
acks: all+min.insync.replicas: 2:保证消息不丢失 -
✅ 启用压缩 (
compression.type: snappy或lz4):减少网络开销 -
✅ Kafka 流式处理用 Kafka Streams Binder,而非手动消费
RabbitMQ 专项
-
✅ 使用
publisher-confirm-type: correlated:保证消息可靠抵达 -
✅ 消费者配置
prefetch: 50-100:平衡吞吐与公平分发 -
✅ 启用 DLX 自动绑定 (
auto-bind-dlq: true)
序列化
-
✅ 统一使用 JSON 序列化 (
content-type: application/json) -
✅ 复杂对象使用
@JsonTypeInfo标注多态,确保反序列化正确 -
✅ 避免使用
application/x-java-object(JDK 序列化):不安全、跨语言不兼容
错误处理
-
✅ 全局 Error Channel 兜底,配合告警系统
-
✅ 自定义
@StreamRetryTemplate控制重试策略 -
✅ 区分临时异常与致命异常:临时异常重试,致命异常直接入 DLQ
-
✅ 死信队列定期监控:DLQ 堆积 = 系统异常
测试
-
✅ 单元测试使用 Test Binder (
TestChannelBinderConfiguration):无需真实 Broker -
✅ 集成测试使用 Testcontainers:拉取真实 Kafka / RabbitMQ 镜像
-
✅ 测试中验证
InputDestination/OutputDestination
运维
-
✅ 启用 Actuator Health Indicator:监控 Binder 连接状态
-
✅ 开启 Micrometer 指标:监控消息吞吐、延迟、错误率
-
✅ 合理设置消费者并发数 :根据分区数设置
concurrency <= partitionCount -
✅ 多实例部署时设置
instance-count:配合分区使用
自定义 Binder
-
✅ Binder 以 Starter 形式发布:使用者只需引入依赖和配置
-
✅ 实现
AbstractMessageBinder:框架已处理大部分通用逻辑 -
✅
META-INF/spring.binders正确声明:框架自动发现 -
✅ 提供
spring.factories自动配置:Spring Boot 自动装配 -
✅ 配置元数据生成 (
spring-boot-configuration-processor):IDE 自动补全
版本选择(JDK 8 环境)
-
✅ Spring Cloud 2021.0.x + Spring Boot 2.7.x:JDK 8 的最终稳定组合
-
✅ Spring Cloud Stream 3.2.x:函数式模型 + JDK 8 完全支持
-
✅ 避免使用 4.x:需要 JDK 17 + Spring Boot 3.x
附录:常用配置速查
Kafka Binder 常用属性
spring.cloud.stream.kafka.binder:
brokers: localhost:9092
auto-create-topics: true
configuration:
security.protocol: SASL_PLAINTEXT # SASL 认证
sasl.mechanism: PLAIN
# 消费者
spring.cloud.stream.kafka.bindings.<name>.consumer:
auto-commit-offset: false
enable-dlq: true
dlq-name: my-dlq-topic
start-offset: earliest # 从头消费
# 生产者
spring.cloud.stream.kafka.bindings.<name>.producer:
sync: true # 同步发送
message-key-expression: payload.id
RabbitMQ Binder 常用属性
spring.cloud.stream.rabbit.binder:
addresses: localhost:5672
spring.cloud.stream.rabbit.bindings.<name>.consumer:
auto-bind-dlq: true
dead-letter-exchange: my.dlx
prefetch: 50
max-concurrency: 10
ttl: 30000 # 消息 TTL (ms)
参考资料:
docs.spring.io/spring-cloud-stream/reference --- 官方参考文档(5.0.1)
Spring Cloud 版本对应 --- Spring Cloud Release Train