Flink DataStream API 基础构件DataStream × Partitioning × ProcessFunction

一、DataStream:你在搭的"数据乐高"

DataStream 是不可变 的数据集抽象(可包含重复元素),既可以是有限 的,也可以是无界 的。与一般集合不同,你不能随意增删元素,只能通过 API 派生新的流。

按"如何分区(partitioned)"来划分,常见 4 类流:

  • Global Stream

    强制单分区/单并行度;很多顺序敏感不支持并发的场景会用到(例如某些老旧外部系统的写入)。

  • Partition Stream (统称:分区流)

    数据被切成多个分区;状态只在分区内可见

    • Keyed Partition Stream每个 key 即一个分区 ,数据归属分区确定
    • Non-Keyed Partition Stream每个并行度视为一个分区 ,归属分区不确定(类似轮询/随机)。
  • Broadcast Stream

    同一份数据复制到每个 下游分区,典型用于规则/字典/配置下发。

重要事实:

  • 一个分区只能被一个任务处理一个任务可以处理多个分区
  • Keyed 流上的状态天然按 key 进行隔离与迁移,是弹性缩扩容的根基。

二、Partitioning:在不同"分区形态"间切换

有了流,还需要在不同分区形态间转换。DataStream 提供 4 种基础分区变换:

  • KeyBy:按指定 key 重分区(NonKeyed → Keyed)。
  • Shuffle:全量打散重分区(常用于均衡负载)。
  • Global:把所有分区合并成一个(强制单并行)。
  • Broadcast:把上游数据复制到下游所有分区(只能与其他输入配合使用)。

代码示例(NonKeyed → Keyed):

java 复制代码
NonKeyedPartitionStream<Tuple<Integer, String>> stream = ...;
KeyedPartitionStream<Integer, String> keyed = stream.keyBy(rec -> rec.f0);

提醒 :Broadcast 不能 直接"转"成其他流,只能作为辅助输入参与下游算子。

三、ProcessFunction:唯一的"处理入口"

对 DataStream 的一切算子处理,都可归结为 ProcessFunction 。它是你定义业务逻辑(含状态/定时器)的唯一入口

3.1 分类(按输入/输出数量)

类型 输入 输出
OneInputStreamProcessFunction 1 1
TwoInputNonBroadcastStreamProcessFunction 2 1
TwoInputBroadcastStreamProcessFunction 2 1
TwoOutputStreamProcessFunction 1 2

多输入/多输出可通过组合 多个 ProcessFunction 实现。
process(...)connectAndProcess(...) 是两个核心入口。

3.2 输入/输出兼容性(单输入)

OneInputStreamProcessFunction:

输入流 输出流
Global Global
Keyed Keyed / NonKeyed
NonKeyed NonKeyed
Broadcast 不支持

TwoOutputStreamProcessFunction:

输入流 输出流
Global Global + Global
Keyed Keyed + Keyed / NonKeyed + NonKeyed
NonKeyed NonKeyed + NonKeyed
Broadcast 不支持

3.3 输入/输出兼容性(双输入)

下表给出两输入之间的兼容与输出类型(❎ 不支持):

输出类型 Global Keyed NonKeyed Broadcast
Global Global
Keyed NonKeyed / Keyed NonKeyed / Keyed
NonKeyed NonKeyed NonKeyed
Broadcast NonKeyed / Keyed NonKeyed

直观理解:

  • Global 非常挑剔(只能和 Global 出结果)。
  • Broadcast 作为输入存在感强,但不能独活(需与另一输入配合)。
  • Keyed 组合最灵活,可输出 Keyed 或 NonKeyed。

四、配置处理节点:withName / withParallelism

process/connectAndProcess返回值既是流,也是可配置句柄,你可以链式设置名称、并行度等属性:

java 复制代码
inputStream
  .process(func1)                 // 处理 1
  .withName("my-process-func")    // 命名
  .withParallelism(2)             // 并行度
  .process(func2);                // 处理 2

建议统一给关键节点命名:方便 UI/日志定位与告警绑定。

五、把"积木"拼起来:三个常用套路

5.1 单输入映射 + 串行落库(Global)

java 复制代码
// 1) 创建环境
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

// 2) Source → NonKeyed
env.fromSource(someSource)
    // 3) 逐条 +1
    .process(new OneInputStreamProcessFunction<Integer, Integer>() {
        @Override
        public void processRecord(Integer x, Collector<Integer> out) throws Exception {
            out.collect(x + 1);
        }
    })
    // 4) 下游不支持并发写:强制单分区
    .global()
    // 5) 落库 / 打印
    .toSink(someSink);

// 6) 触发
env.execute();

5.2 规则广播 + 事件主流(Broadcast + Keyed)

典型:规则/黑名单/阈值通过 Broadcast 下发,事件按 key 处理。

java 复制代码
NonKeyedPartitionStream<Rule> rules = env.fromSource(ruleSource).broadcast();
KeyedPartitionStream<String, Event> events = env.fromSource(eventSource)
                                                .keyBy(e -> e.userId);

events
  .connectAndProcess(
    rules,
    new TwoInputBroadcastStreamProcessFunction<Event, Rule, Alert>() {
      @Override
      public void processElement(Event ev, Context ctx, Collector<Alert> out) {
        Rule r = /* 从广播状态读取相应规则 */;
        if (r.match(ev)) out.collect(toAlert(ev, r));
      }
      @Override
      public void processBroadcastElement(Rule r, Context ctx, Collector<Alert> out) {
        /* 更新广播状态中的规则 */
      }
    })
  .withName("event-with-rules")
  .withParallelism(8)
  .toSink(alertSink);

记住:Broadcast 流不能单独转换,必须配合另一输入。

5.3 单输入双路输出(TwoOutput)

典型:按条件分流(如异常→告警流,正常→明细流)。

java 复制代码
env.fromSource(someSource)
   .process(new TwoOutputStreamProcessFunction<Event, Event, Alert>() {
     @Override
     public void processRecord(Event e, Collector<Event> main, Collector<Alert> side) {
       if (isAnomaly(e)) side.collect(toAlert(e));
       else main.collect(e);
     }
   })
   .withName("split")
   .withParallelism(4);

// 假设框架提供对两个输出的后续接法(略)

六、设计选型指南(3 步走)

  1. 先判定"分区形态"

    • 需要按用户/订单维度关帐?→ Keyed
    • 只是并行提升吞吐?→ NonKeyed + 必要时 Shuffle
    • 下游不支持并发?→ Global
    • 动态规则/字典?→ Broadcast + 另一条输入。
  2. 再挑 ProcessFunction 形态

    • 单流处理:OneInputStreamProcessFunction
    • 双流汇合:TwoInput*(是否含 Broadcast 取决于场景)
    • 需要分两路输出:TwoOutputStreamProcessFunction
  3. 最后补齐配置与产线能力

    • withName/withParallelism、异常处理、幂等/事务、监控与告警。

七、工程化最佳实践与避坑

  • 状态作用域只在分区内:跨 key 的逻辑请谨慎(必要时引入 Global 辅助或外部存储)。
  • Broadcast 不能独活:只能作为双输入之一参与处理。
  • Global 慎用:是"单核"开关,会成为吞吐瓶颈;只在确实需要串行时使用。
  • 命名与观测 :所有关键处理节点都要 withName,方便 UI/日志/报警。
  • 分流一致性:TwoOutput 的两路要各自保证语义(例如都写成功/失败时的补偿策略)。
  • 外部写入 :不支持并发的 Sink → global();支持并发但要幂等/事务保障。
  • 压测先行 :对 Keyed 流关注热点 key;必要时做前缀打散或二级键拆分。

八、一页速查表(Cheat-Sheet)

分区变换:

  • keyBy:NonKeyed → Keyed(确定路由)
  • shuffle:打散重分区(均衡负载)
  • global:合并为单分区(串行)
  • broadcast:复制到所有分区(需与另一输入连用)

ProcessFunction 选择:

  • 单输入单输出:OneInputStreamProcessFunction
  • 单输入双输出:TwoOutputStreamProcessFunction
  • 双输入:TwoInputNonBroadcastStreamProcessFunction / TwoInputBroadcastStreamProcessFunction

兼容规则要点:

  • Global 基本只和 Global 兼容。
  • Broadcast 只能作为双输入之一;输出通常是 Keyed/NonKeyed
  • Keyed 组合最灵活,产出 Keyed 或 NonKeyed。

九、结语

把 DataStream、Partitioning、ProcessFunction 三块"地基"吃透,你就具备了自下而上搭建任何实时拓扑的能力:

  • 分区形态 把并行与一致性讲清楚;
  • ProcessFunction 把计算与状态讲扎实;
  • withName/withParallelism 把工程生产化。
相关推荐
今日说"法"3 小时前
Rust 内存泄漏的检测与防范:超越安全的实践指南
java·安全·rust
wudl55663 小时前
Flink Keyed State 详解之五
大数据·flink
欠你一个bug3 小时前
Java设计模式应用--装饰器模式
java·设计模式·装饰器模式
兔兔爱学习兔兔爱学习8 小时前
Spring Al学习7:ImageModel
java·学习·spring
lang201509289 小时前
Spring远程调用与Web服务全解析
java·前端·spring
m0_5642641810 小时前
IDEA DEBUG调试时如何获取 MyBatis-Plus 动态拼接的 SQL?
java·数据库·spring boot·sql·mybatis·debug·mybatis-plus
崎岖Qiu10 小时前
【设计模式笔记06】:单一职责原则
java·笔记·设计模式·单一职责原则
Hello.Reader10 小时前
Flink ExecutionConfig 实战并行度、序列化、对象重用与全局参数
java·大数据·flink
熊小猿11 小时前
在 Spring Boot 项目中使用分页插件的两种常见方式
java·spring boot·后端