2. Flink基础回顾
在进入事件时间与多流关联之前,有必要把 Flink 运行时最核心的一层重新梳理清楚。很多“窗口不触发”“水位线不推进”“关联结果为空”的问题,根源并不在窗口本身,而在于对 DataStream API 的执行模型、并行度与资源分配理解不到位。本节围绕六个关键词展开:DataStream API、执行环境、数据源与数据汇、转换算子、并行度与算子链、任务槽与资源模型。
一、核心原理:从程序到分布式执行
Flink 程序本质上是“惰性构建 + 显式触发”的两段式结构。你在 main 方法里调用 env.addSource(...)、map、keyBy、window 时,只是在构建一张逻辑数据流图(StreamGraph),并不会立刻执行。只有调用 env.execute() 时,Flink 才会把逻辑图翻译成 JobGraph,提交到集群,再由 JobManager 生成 ExecutionGraph 并调度到 TaskManager 上运行。
执行环境 StreamExecutionEnvironment 是这一切的入口。它决定了作业运行在本地还是集群、默认并行度是多少、是否开启检查点。数据源(Source)负责把外部数据引入流,转换算子(Transformation)负责加工,数据汇(Sink)负责把结果写出。三者之间通过数据流连接,而并行度决定了每个算子被拆成多少个并行子任务。
二、方法流程:一个最小可运行骨架
下面这段代码覆盖了执行环境、数据源、转换算子、数据汇与并行度设置,是后续所有实战章节的模板:
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(2);
env.enableCheckpointing(10_000);
DataStream<String> lines = env.addSource(new FlinkKafkaConsumer<>(
"topic_events", new SimpleStringSchema(), props));
DataStream<Event> events = lines
.map(new ParseJsonMap())
.filter(e -> e != null)
.name("parse-and-filter");
events.keyBy(Event::getUserId)
.window(TumblingEventTimeWindows.of(Time.seconds(10)))
.aggregate(new CountAgg())
.addSink(new FlinkKafkaProducer<>("topic_result", schema, props));
env.execute("flink-basics-demo");
流程可以归纳为四步:建环境 → 接数据源 → 串转换算子 → 写数据汇并 execute。其中 name() 建议显式命名,便于在 Web UI 中定位算子;setParallelism() 可作用于整个作业,也可单独作用于某个算子。
三、并行度、算子链与任务槽
并行度(Parallelism)决定一个算子被拆成多少个子任务。算子链(Operator Chaining)是 Flink 的优化:当相邻算子并行度相同、且没有 shuffle 时,Flink 会把它们合并成一个算子链,放在同一个线程中执行,减少线程切换与序列化开销。例如 Source → map → filter 若并行度一致,通常会被链在一起。
任务槽(Task Slot)是 TaskManager 上的资源单位。一个 TaskManager 有若干 slot,每个 slot 可以运行一条算子链。slot 数量决定了单个 TaskManager 能并行执行多少条链。资源模型上,Flink 采用“slot 共享”机制:同一个 slot 中可以运行同一个作业不同算子的子任务,从而提高资源利用率。
| 概念 | 作用 | 常见取值/建议 |
|---|---|---|
| 并行度 | 算子子任务数量 | 与 Kafka 分区数对齐,避免空闲 |
| 算子链 | 合并相邻算子减少开销 | 默认开启,可用 disableChaining 关闭 |
| 任务槽 | TaskManager 的资源单位 | slot 数 ≥ 单作业最大并行度 |
| Slot 共享 | 同作业子任务共享 slot | 默认开启,提升利用率 |
四、误区与复盘
- 误区一:并行度越高越快。并行度超过数据源分区数时,多出的子任务会空闲,反而增加调度开销。
- 误区二:忘记 execute。只构建 DataStream 不调用
env.execute(),作业不会提交,日志中也不会有任何运行痕迹。 - 误区三:混淆算子链与并行度。算子链是执行优化,并行度是任务拆分,两者维度不同,不能互相替代。
- 误区四:slot 不足导致作业卡在 created 状态。此时应检查 TaskManager 数量与 slot 配置,而不是反复重启作业。
把这一层基础打牢,后面讨论水位线推进、窗口触发与双流 join 时,你就能清楚地区分:哪些现象是时间语义问题,哪些其实是并行度、算子链或资源模型配置不当造成的。