4、实时计算引擎:Apache Flink核心概念、流处理与批处理统一、Checkpoint机制
好,咱们进入第四章。这一章聊的是实时计算引擎的扛把子——Apache Flink。说实话,在Lakehouse体系里,Flink的地位有点像“发动机”。没有它,实时链路就转不起来。
我最早接触Flink是2018年,那时候公司要从Storm迁移。当时觉得Flink的文档写得真不错,但真正上手才发现,坑也不少。今天我把这些经验揉碎了讲给你听。
4.1 Flink的核心概念:你必须要搞懂的三个东西
Flink的概念很多,但核心的就三个:流(Stream)、算子(Operator)、状态(State)。你把这仨搞明白了,Flink就懂了一半。
4.1.1 流(Stream)—— 数据流动的管道
流,说白了就是一条永不停歇的数据管道。数据从源头(比如Kafka)进来,经过各种处理,最后流到目标(比如HDFS或数据库)。
Flink把流分成两种:
| 类型 | 说明 | 典型场景 |
|---|---|---|
| 有界流(Bounded Stream) | 数据有明确的起点和终点,处理完就结束 | 离线批处理、历史数据回放 |
| 无界流(Unbounded Stream) | 数据源源不断,没有终点 | 实时日志、传感器数据、交易流水 |
嗯,这里要注意:Flink对这两种流的处理逻辑是统一的。你写一套代码,既能跑批也能跑流。这个后面会细说。
4.1.2 算子(Operator)—— 处理逻辑的单元
算子就是你对数据做的操作。Flink提供了丰富的算子,我列几个最常用的:
- Map:一对一转换,比如把JSON解析成Java对象
- FlatMap:一对多转换,比如把一句话拆成单词
- KeyBy:按某个字段分组,类似SQL里的GROUP BY
- Window:开窗口,比如每5分钟统计一次
- ProcessFunction:最灵活的算子,可以访问状态和定时器
我个人习惯把ProcessFunction当作“瑞士军刀”。遇到复杂逻辑,用它准没错。
4.1.3 状态(State)—— Flink的灵魂
状态是什么?就是算子在处理过程中需要记住的信息。比如你要统计每个用户的点击次数,那“当前计数”就是状态。
Flink的状态分为两种:
- Keyed State:按Key划分的状态,每个Key有自己的状态
- Operator State:算子级别的状态,所有数据共享
4.2 流处理与批处理统一:一套代码,两种模式
Flink最让我佩服的一点,就是它实现了流批一体。什么意思?你写一套DataStream API或者Table API的代码,既可以跑在流模式,也可以跑在批模式。
为什么会这样?因为Flink把批处理看作“有界流”的特例。批处理的数据有起点和终点,Flink就把它当作一个有限的数据流来处理。
4.2.1 运行时模式对比
| 维度 | 流模式(Streaming) | 批模式(Batch) |
|---|---|---|
| 数据特性 | 无界,持续到达 | 有界,一次性加载 |
| 调度方式 | 事件驱动,来一条处理一条 | 阶段式调度,分阶段执行 |
| 容错机制 | 基于Checkpoint的精确一次 | 失败后重试整个批次 |
| 适用场景 | 实时ETL、监控告警 | 历史数据重跑、报表计算 |
你看,底层机制不同,但上层API是统一的。这就是Flink的设计哲学。
4.2.2 代码示例:流批一体的WordCount
我写个简单的例子,你感受一下:
// 这段代码既能在流模式跑,也能在批模式跑
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 读取数据源(可以是Kafka流,也可以是文件)
DataStream<String> text = env.readTextFile("input.txt");
// 处理逻辑完全一样
DataStream<WordCount> counts = text
.flatMap(new Tokenizer())
.keyBy(word -> word.word)
.sum("count");
counts.print();
env.execute("Stream-Batch Unified WordCount");
你想想看,以前做实时和离线要维护两套代码,现在一套搞定。维护成本直接砍半。
4.3 Checkpoint机制:Flink的“后悔药”
做实时计算,最怕什么?机器挂了,数据丢了。Flink的Checkpoint机制就是解决这个问题的。
Checkpoint说白了就是给Flink任务拍“快照”。每隔一段时间,把当前的状态和位置保存下来。如果任务挂了,就从最近一次成功的Checkpoint恢复。
4.3.1 Checkpoint的工作原理
我画个图帮你理解:
流程是这样的:
- Barrier注入:Checkpoint Coordinator向Source算子注入一个Barrier(屏障)
- Barrier传递:Barrier随着数据流向下游传递,每个算子收到Barrier后,开始做状态快照
- 状态持久化:算子把当前状态写入State Backend(比如RocksDB或HDFS)
- 完成确认:所有算子完成快照后,Coordinator标记这次Checkpoint成功
4.3.2 精确一次语义(Exactly-Once)
Flink的Checkpoint配合Kafka的幂等写入,可以实现精确一次语义。什么意思?就是每条数据恰好被处理一次,不多也不少。
实现原理:
- Source端:记录Kafka Offset到Checkpoint中
- Sink端:开启两阶段提交(Two-Phase Commit)
- 恢复时:从Checkpoint恢复Offset,回滚未提交的事务
你想想看,金融场景下,少一条或多一条都是事故。Flink的精确一次就是给这种场景兜底的。
4.3.3 Checkpoint配置实战
我贴一段生产环境的配置,你直接参考:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 开启Checkpoint,间隔30秒
env.enableCheckpointing(30000);
// 设置模式为精确一次
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// Checkpoint超时时间
env.getCheckpointConfig().setCheckpointTimeout(60000);
// 同时进行的Checkpoint数量
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
// 两次Checkpoint之间的最小间隔
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(10000);
// 任务取消后保留Checkpoint
env.getCheckpointConfig().enableExternalizedCheckpoints(
CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION
);
// 设置State Backend为RocksDB
env.setStateBackend(new RocksDBStateBackend("hdfs://namenode/flink/checkpoints"));
4.4 本章小结
这一章我们聊了Flink的三个核心:流、算子、状态。重点讲了流批一体的设计理念,以及Checkpoint这个“后悔药”机制。
说实话,Flink的深度远不止这些。但你把今天的内容吃透了,至少能应付80%的实时计算场景。下一章我们会聊Flink在Lakehouse中的具体应用,包括如何与Kafka、Hudi集成。
嗯,今天就到这里。有问题随时交流。