Spring Cloud Stream 最佳实践指南

目录

  1. 概述

  2. [版本选择与 Maven 依赖](#版本选择与 Maven 依赖)

  3. 核心概念

  4. 编程模型

  5. 配置体系

  6. [Binder 实战](#Binder 实战)

    • 6.1 [Kafka Binder](#Kafka Binder)

    • 6.2 [RabbitMQ Binder](#RabbitMQ Binder)

    • 6.3 [多 Binder 共存](#多 Binder 共存)

  7. 消息转换与序列化

  8. 错误处理与死信队列

  9. 测试支持

  10. 完整示例

  11. [自定义 Binder Starter 开发](#自定义 Binder Starter 开发)

  12. 最佳实践清单


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 中间件适配层,负责与具体消息系统交互 kafkarabbit
Binding 应用输入/输出与中间件 Destination 之间的桥梁 inputoutputprocess-inprocess-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-0uppercase-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: snappylz4):减少网络开销

  • 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 BinderTestChannelBinderConfiguration):无需真实 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)

参考资料: