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):

NonKeyedPartitionStream<Tuple<Integer, String>> stream = ...;
  KeyedPartitionStream<Integer, String> keyed = stream.keyBy(rec -> rec.f0);

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

三、ProcessFunction:唯一的“处理入口”

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

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

类型输入输出
OneInputStreamProcessFunction11
TwoInputNonBroadcastStreamProcessFunction21
TwoInputBroadcastStreamProcessFunction21
TwoOutputStreamProcessFunction12

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

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

OneInputStreamProcessFunction:

输入流输出流
GlobalGlobal
KeyedKeyed / NonKeyed
NonKeyedNonKeyed
Broadcast不支持

TwoOutputStreamProcessFunction:

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

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

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

输出类型GlobalKeyedNonKeyedBroadcast
GlobalGlobal
KeyedNonKeyed / KeyedNonKeyed / Keyed
NonKeyedNonKeyedNonKeyed
BroadcastNonKeyed / KeyedNonKeyed

直观理解:

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

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

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

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

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

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

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

// 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 处理。

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)

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

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 把工程生产化。
posted on 2025-11-26 08:06  ljbguanli  阅读(0)  评论(0)    收藏  举报