2、Spring Cloud Stream 函数式模型中 Supplier 是如何“主动推送”消息的?

🔍 深度解析:Supplier 是如何“主动推送”消息的?频率?如何手动触发?

一、Supplier 是如何被“自动调用”的?—— 框架的“事件驱动”机制

✅ 核心结论:

Spring Cloud Stream 并非通过定时器轮询调用 Supplier,而是通过 Spring Integration 的 Polling Consumer 机制,在后台启动一个“轮询器”(Poller),周期性地调用 Supplier 的 get() 方法。

🧠 详细原理:

1. Supplier 是一个“消息源”(Message Source)

在 Spring Cloud Stream 中,Supplier<T> 被框架包装成一个 MessageSource<T> 实例,注册为 Spring Integration 的 Polling Consumer 的源

  • 当你声明:
    @Bean
    public Supplier<Order> orderSupplier() {
        return () -> new Order("ORD-123", "iPhone", 9999L);
    }
    
  • 框架会自动创建一个 IntegrationFlow,结构如下:
IntegrationFlow flow = IntegrationFlow.from(orderSupplier, 
    c -> c.poller(Pollers.fixedDelay(5000))) // 默认5秒轮询
    .channel("orderSupplier-out-0")
    .get();

📌 关键点Supplier 不是“事件驱动”(如 HTTP 请求触发),而是被动轮询(Polling)触发!

2. 轮询器(Poller)是核心
  • Spring Integration 默认使用 Poller(轮询器)定期调用 Supplier.get()
  • 每次调用 get(),如果返回非 null 值,框架会将其封装为 Message<T>,并发送到绑定的输出通道(orderSupplier-out-0),最终写入 Kafka/RabbitMQ。
  • 如果返回 null,则不发送任何消息(相当于“跳过本次轮询”)。
3. 为什么不是“事件驱动”?
  • 消息队列(Kafka/RabbitMQ)是异步、解耦、持久化的系统。
  • “事件驱动”通常指外部事件(如 REST 请求、数据库变更)触发消息发送。
  • Supplier 的设计目标是:周期性地产生事件(如定时上报心跳、定时生成统计、定时拉取数据)。
  • 如果你想“由外部事件触发”,你应该使用 StreamBridge(见后文)。

💡 类比:Supplier 就像一个“定时任务”(@Scheduled),只不过它自动把结果发到消息队列。


二、频率是怎样的?如何配置轮询间隔?

✅ 默认频率:5 秒

Spring Cloud Stream 的默认轮询间隔是 5000 毫秒(5秒)

✅ 如何修改轮询频率?

方法 1:通过配置文件(推荐)
spring:
  cloud:
    stream:
      bindings:
        orderSupplier-out-0:
          destination: orders-created
          content-type: application/json
          producer:
            # ⬇️ 关键配置:设置轮询间隔(单位:毫秒)
            poller:
              fixed-delay: 2000   # 每2秒调用一次 Supplier.get()
              max-messages-per-poll: 1 # 每次轮询最多发送1条消息(默认1)

producer.poller.fixed-delay 是控制 Supplier 调用频率的唯一方式!

方法 2:通过 Java 配置(高级,更灵活)

如果你需要更复杂的轮询策略(如固定速率、自定义错误处理),可以自定义 Poller

@Bean
public Supplier<Order> orderSupplier() {
    return () -> new Order("ORD-" + System.currentTimeMillis(), "iPhone", 9999L);
}

@Bean
public IntegrationFlow orderSupplierFlow(Supplier<Order> supplier) {
    return IntegrationFlow.from(supplier, 
        c -> c.poller(Pollers.fixedDelay(1000) // 1秒
                      .maxMessagesPerPoll(1)
                      .errorHandler(error -> System.err.println("Supplier调用失败: " + error.getMessage()))))
        .channel("orderSupplier-out-0")
        .get();
}

⚠️ 注意:使用 Java 配置时,必须确保绑定名称与函数名一致orderSupplier-out-0),否则框架会创建两个通道,导致消息重复或丢失。

✅ 常用轮询配置选项:
配置项说明示例
fixed-delay固定延迟轮询(推荐)2000(2秒)
fixed-rate固定速率轮询(从上一次开始计时)1000(每秒一次)
cron使用 Cron 表达式(如每天凌晨)"0 0 2 * * ?"(每天2点)
max-messages-per-poll每次轮询最多发送多少条5(批量发送)
trigger自定义触发器(如基于时间窗口)需实现 Trigger 接口

📌 推荐使用 fixed-delay:它确保每次调用完成后,等待指定时间再下一次调用,避免堆积。

✅ 示例:每天凌晨 2 点生成日统计报告
spring:
  cloud:
    stream:
      bindings:
        dailyReportSupplier-out-0:
          producer:
            poller:
              cron: "0 0 2 * * ?" # 每天2:00:00
@Bean
public Supplier<Report> dailyReportSupplier() {
    return () -> {
        // 生成昨日订单统计
        return new Report(LocalDate.now().minusDays(1), orderService.countYesterdayOrders());
    };
}

三、如何实现“手动发送消息”?—— 用 StreamBridge

❌ 误区:不能直接调用 supplier.get() 手动触发!

不能在 Controller 里这样写:

@RestController
class OrderController {

    @Autowired
    private Supplier<Order> supplier; // ❌ 错误!这是个函数,不是服务

    @PostMapping("/create-order")
    public void createOrder(@RequestBody Order order) {
        supplier.get(); // ❌ 这只会调用一次,但不会发送到 Kafka!
        // 因为 Supplier 的调用是被 Poller 控制的,你调用它不会触发绑定通道
    }
}

✅ 正确做法:使用 StreamBridge

StreamBridge 是 Spring Cloud Stream 提供的官方 API,用于在运行时、手动、按需向绑定通道发送消息。

1. 注入 StreamBridge
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @Autowired
    private StreamBridge streamBridge; // ✅ 官方推荐的“手动发送”工具

    @PostMapping
    public ResponseEntity<String> createOrder(@RequestBody Order order) {
        // ✅ 手动发送消息到名为 "orders-created" 的绑定通道
        boolean sent = streamBridge.send("orders-created", order);

        if (sent) {
            return ResponseEntity.ok("订单已发送到消息队列");
        } else {
            return ResponseEntity.status(500).body("发送失败");
        }
    }
}
2. 为什么 StreamBridge 能手动发送?
  • StreamBridge 内部封装了 Spring Integration 的 MessageChannel
  • 它直接调用 messageChannel.send(message)绕过轮询器,实现即时、同步、按需发送。
  • 它不依赖 Supplier,完全独立。
3. StreamBridge 支持多种发送方式:
方法用途
send(String bindingName, Object payload)发送普通对象(自动序列化)
send(String bindingName, Message<?> message)发送完整 Message(可自定义 header)
send(String bindingName, Object payload, Map<String, Object> headers)发送带自定义 header 的消息
✅ 示例:发送带自定义 Header 的消息(用于追踪)
@PostMapping("/create-order-with-trace")
public ResponseEntity<String> createOrderWithTrace(@RequestBody Order order) {
    Map<String, Object> headers = Map.of(
        "trace-id", UUID.randomUUID().toString(),
        "source", "order-service"
    );

    boolean sent = streamBridge.send("orders-created", order, headers);

    return ResponseEntity.ok(sent ? "已发送" : "失败");
}
✅ 示例:发送 Message 对象(完全控制)
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;

@PostMapping("/create-order-as-message")
public ResponseEntity<String> createOrderAsMessage(@RequestBody Order order) {
    Message<Order> message = MessageBuilder
        .withPayload(order)
        .setHeader("correlation-id", "C12345")
        .setHeader("priority", 1)
        .build();

    boolean sent = streamBridge.send("orders-created", message);
    return ResponseEntity.ok(sent ? "成功" : "失败");
}

四、Supplier vs StreamBridge:使用场景对比

场景推荐方案原因
✅ 定时生成事件(如每分钟上报系统状态)Supplier自动轮询,无需人工干预
✅ 每日生成统计报表Supplier + cron配置简单,适合定时任务
✅ 用户下单后立即发送订单事件StreamBridge必须“立即”响应,不能等5秒
✅ REST API 触发事件StreamBridge用户请求 → 立即发消息
✅ 数据库变更后发送 CDC 事件(如 Debezium)StreamBridge由监听器触发,非周期性
✅ 批量处理后发送多条消息StreamBridge + 循环可控制发送时机和数量

黄金法则

  • 周期性、自动化 → 用 Supplier
  • 用户触发、即时响应 → 用 StreamBridge

五、实战:结合 Supplier + StreamBridge 构建完整订单系统

// 1. 定时生成测试订单(Supplier)
@Bean
public Supplier<Order> orderSupplier() {
    return () -> {
        // 模拟每3秒生成一个测试订单(用于演示)
        return new Order("TEST-" + System.nanoTime(), "测试商品", 100L);
    };
}

// 2. 手动创建真实订单(StreamBridge)
@RestController
public class OrderController {

    @Autowired
    private StreamBridge streamBridge;

    @PostMapping("/orders")
    public ResponseEntity<String> createOrder(@RequestBody Order order) {
        // ✅ 手动发送真实订单事件
        boolean sent = streamBridge.send("orders-created", order);
        if (sent) {
            return ResponseEntity.ok("订单已创建,事件已发送");
        }
        return ResponseEntity.status(500).body("发送失败");
    }
}

// 3. 消费者(统一处理所有订单)
@Bean
public Consumer<Order> orderConsumer() {
    return order -> {
        System.out.println("✅ 收到订单: " + order);
        // 扣库存、发通知、写数据库...
    };
}

✅ 配置文件(application.yml)

spring:
  cloud:
    stream:
      bindings:
        orderSupplier-out-0:
          destination: orders-created
          producer:
            poller:
              fixed-delay: 3000  # 每3秒生成一个测试订单

        # 注意:Supplier 和 StreamBridge 使用同一个 destination
        # orderConsumer-in-0 也绑定到 orders-created
        orderConsumer-in-0:
          destination: orders-created
          group: order-consumer-group

      kafka:
        binder:
          brokers: localhost:9092
          configuration:
            auto.create.topics.enable: true
            key.serializer: org.apache.kafka.common.serialization.StringSerializer
            value.serializer: org.springframework.kafka.support.serializer.JsonSerializer
            key.deserializer: org.apache.kafka.common.serialization.StringDeserializer
            value.deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
            spring.json.trusted.packages: com.example.model

📊 运行效果:

  • 每3秒:Supplier 自动发送一条 TEST-xxx 订单
  • 你调用 POST /orders:发送一条真实订单
  • 所有订单都被同一个 orderConsumer 消费

✅ 你实现了“自动 + 手动”双通道事件生产,完美解耦!


六、进阶:如何实现“只在特定条件下发送”?

你可能希望:只有当订单金额 > 1000 时才发送

✅ 方案1:在 Supplier 中过滤(适用于周期性场景)

@Bean
public Supplier<Order> orderSupplier() {
    return () -> {
        Order order = generateRandomOrder(); // 生成随机订单
        return order.price() > 1000 ? order : null; // ✅ 只发送高价值订单
    };
}

✅ 方案2:在 Controller 中判断(适用于手动触发)

@PostMapping("/orders")
public ResponseEntity<String> createOrder(@RequestBody Order order) {
    if (order.price() < 1000) {
        return ResponseEntity.badRequest().body("订单金额需大于1000");
    }
    boolean sent = streamBridge.send("orders-created", order);
    return ResponseEntity.ok(sent ? "发送成功" : "发送失败");
}

✅ 业务逻辑应放在 Controller 或 Service 层,而不是绑定层。


七、性能与注意事项

问题建议
Supplier 轮询太频繁?避免低于 1000ms,防止 Kafka 压力过大
StreamBridge 发送失败?默认是同步发送,若 Kafka 不可达会抛异常 → 建议加 try-catch 或使用异步发送
如何异步发送?streamBridge.send() 是同步的。若需异步,可包装在 @Async 方法中
如何监控发送成功率?启用 Actuator,查看 stream.outbound.orders-created.messages 指标
Supplier 返回 null 会怎样?框架忽略,不发送消息,不报错,不计数
多个 Supplier?每个 Supplier 必须有唯一绑定名(supplier1-out-0, supplier2-out-0

✅ 总结:Supplier 的真相与最佳实践

问题答案
Supplier 是怎么被调用的?由 Spring Integration 的 Poller 周期性调用,不是事件驱动,也不是定时器注解(@Scheduled)
默认频率是多少?5 秒(可通过 producer.poller.fixed-delay 修改)
如何手动发送?使用 StreamBridge.send("bindingName", payload),这是官方推荐方式
Supplier 和 StreamBridge 如何选择?定时任务 → Supplier;用户请求/外部事件 → StreamBridge
能用 @Scheduled 替代 Supplier 吗?可以,但不推荐 —— Supplier 更符合 Stream 的抽象模型,且自动集成监控和绑定
Supplier 返回 null 会被记录吗?不会,不会发送消息,也不会计入指标
如何实现“只在条件满足时发送”?在 Supplier 中返回 null,或在 Controller 中校验后调用 StreamBridge

🚀 最佳实践总结(一句话版)

“用 Supplier 做定时事件,用 StreamBridge 做即时事件,别混用,别手动调 Supplier.get()。”


🔗 推荐阅读


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值