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:算子级别的状态,所有数据共享
我的经验:状态大小要控制好。我曾经遇到过一个任务,状态涨到几十GB,导致Checkpoint超时。后来发现是Key设计不合理,把用户ID和事件类型拼接在一起,导致状态膨胀。改成只按用户ID分组后,状态降到了几百MB。

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");

你想想看,以前做实时和离线要维护两套代码,现在一套搞定。维护成本直接砍半。

关键点:流批一体的前提是,你的业务逻辑不依赖“无限等待”的语义。比如你要做全量排序,流模式就不太合适。但绝大多数ETL场景,流批一体完全够用。

4.3 Checkpoint机制:Flink的“后悔药”

做实时计算,最怕什么?机器挂了,数据丢了。Flink的Checkpoint机制就是解决这个问题的。

Checkpoint说白了就是给Flink任务拍“快照”。每隔一段时间,把当前的状态和位置保存下来。如果任务挂了,就从最近一次成功的Checkpoint恢复。

4.3.1 Checkpoint的工作原理

我画个图帮你理解:

Flink Checkpoint 工作流程 Kafka Source Map算子 Window算子 HDFS Sink Checkpoint Coordinator Source Offset Map State Window State State Backend(RocksDB / HDFS) Barrier对齐机制 所有算子收到Barrier 后才开始快照 保证全局一致性

流程是这样的:

  1. Barrier注入:Checkpoint Coordinator向Source算子注入一个Barrier(屏障)
  2. Barrier传递:Barrier随着数据流向下游传递,每个算子收到Barrier后,开始做状态快照
  3. 状态持久化:算子把当前状态写入State Backend(比如RocksDB或HDFS)
  4. 完成确认:所有算子完成快照后,Coordinator标记这次Checkpoint成功
我曾经踩过的坑:Barrier对齐机制在数据倾斜时会导致反压。比如某个分区的数据量特别大,Barrier迟迟过不去,整个任务就卡住了。解决方案是开启Unaligned 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"));
我的建议:Checkpoint间隔不要设太短。我见过有人设5秒,结果状态频繁快照,CPU全耗在序列化上了。一般30秒到1分钟比较合理。如果状态特别大,可以适当延长。

4.4 本章小结

这一章我们聊了Flink的三个核心:流、算子、状态。重点讲了流批一体的设计理念,以及Checkpoint这个“后悔药”机制。

说实话,Flink的深度远不止这些。但你把今天的内容吃透了,至少能应付80%的实时计算场景。下一章我们会聊Flink在Lakehouse中的具体应用,包括如何与Kafka、Hudi集成。

嗯,今天就到这里。有问题随时交流。