第1章:Flink核心原理与架构
大家好,我是这次课程的主讲。今天咱们聊聊Flink的底层逻辑。说实话,我见过不少团队,代码写得挺溜,但一遇到故障就抓瞎。为什么?因为没搞懂Flink到底是怎么工作的。你想想看,一个黑盒系统出了问题,你连日志都看不懂,那还怎么排查?
所以第一章,咱们先把地基打牢。我会结合我这些年踩过的坑,把Flink的架构、数据流、时间语义、状态管理和Checkpoint机制,掰开了揉碎了讲清楚。
1.1 Flink架构:从JobManager到TaskManager
Flink的架构,说白了就是一个“领导+干活”的模式。领导是JobManager,干活的是TaskManager。
- JobManager:负责调度、协调、Checkpoint的触发。它不处理具体数据,但所有决策都靠它。
- TaskManager:真正干活的。每个TaskManager里有多个Slot,每个Slot跑一个子任务。
我个人习惯把JobManager比作“项目经理”,TaskManager比作“程序员”。项目经理不写代码,但他知道谁该干什么,什么时候该检查进度。
避坑指南:我曾经遇到过一个线上故障,JobManager频繁Full GC,导致整个集群无法触发Checkpoint。后来发现是用户代码里有个无限增长的HashMap,把JobManager的堆撑爆了。所以,JobManager的堆内存一定要单独监控,别和TaskManager混为一谈。
这里我画了一张架构图,帮你直观理解:
1.2 数据流图:DAG是怎么跑起来的
Flink把我们的代码翻译成一个DAG(有向无环图)。这个图里,每个节点是一个算子,每条边是数据流动的方向。
举个例子,一个简单的WordCount:
DataStream<String> lines = env.addSource(new FlinkKafkaConsumer<>(...));
DataStream<Tuple2<String, Integer>> counts = lines
.flatMap(new Tokenizer())
.keyBy(0)
.sum(1);
counts.addSink(new StreamingFileSink<>(...));
这段代码会被Flink拆成四个阶段:Source → FlatMap → KeyBy/Sum → Sink。每个阶段可以并行跑多个实例。
我的经验:排查故障时,第一步就是看DAG图。Flink Web UI上那个图,能告诉你每个算子的数据量、延迟、反压情况。我曾经靠这个图,一眼就发现某个FlatMap算子处理速度远低于上游,直接定位到是正则表达式写得有问题。
1.3 时间语义:Event Time、Ingestion Time、Processing Time
时间,是流计算里最容易出幺蛾子的地方。Flink支持三种时间:
| 时间类型 | 含义 | 典型场景 | 坑点 |
|---|---|---|---|
| Event Time | 事件发生的时间(业务时间) | 金融交易时间、用户点击时间 | 乱序、迟到数据 |
| Ingestion Time | 数据进入Flink的时间 | 对时间精度要求不高的场景 | 与机器时钟绑定 |
| Processing Time | 算子处理数据的当前时间 | 实时监控、报警 | 结果不可重复 |
我个人强烈建议:金融场景一律用Event Time。为什么?因为交易时间才是业务关心的,而不是服务器处理的时间。你想想看,如果因为网络延迟,数据晚到了10秒,你用Processing Time去算,那结果就全错了。
注意:Event Time必须配合Watermark使用。Watermark是Flink用来判断“数据是否到齐”的机制。我曾经见过一个案例,Watermark设置得太激进,导致大量正常数据被当作迟到数据丢弃。后来我们改成了基于业务峰值的动态Watermark,才解决问题。
1.4 状态管理:Flink的“记忆”能力
流计算为什么比批处理难?因为流计算有状态。状态就是算子的“记忆”。比如你要算过去5分钟的累计交易额,那这5分钟内的中间结果就是状态。
Flink的状态分为两种:
- Keyed State:按Key划分的状态,比如每个用户的持仓信息。
- Operator State:算子级别的状态,比如Kafka的Offset。
状态后端也有三种选择:
| 状态后端 | 存储位置 | 适用场景 |
|---|---|---|
| MemoryStateBackend | TaskManager内存 | 小状态、调试 |
| FsStateBackend | 本地文件 + 远程文件系统 | 中等状态、生产环境 |
| RocksDBStateBackend | RocksDB(本地磁盘) | 超大状态(TB级别) |
避坑指南:我曾经接手过一个项目,状态大小超过100GB,用的却是MemoryStateBackend。结果每次Checkpoint都OOM,任务频繁重启。后来改成RocksDBStateBackend,问题立刻解决。记住:状态大小超过10GB,就别用内存了。
1.5 Checkpoint机制:Flink的“后悔药”
Checkpoint是Flink容错的核心。它定期给所有算子的状态拍一张“快照”。一旦任务挂了,就从最近的Checkpoint恢复。
它的工作流程是这样的:
- JobManager向Source算子注入一个Barrier(屏障)。
- Barrier随着数据流往下游传播。
- 每个算子收到Barrier后,把当前状态快照保存到远程存储(如HDFS)。
- 所有算子都完成快照后,这个Checkpoint就算成功了。
这里有个关键点:Barrier对齐。如果一个算子有多个输入流,它必须等到所有输入流的Barrier都到齐,才能做快照。这期间,先到的数据会被缓存起来。
我的经验:排查Checkpoint失败时,我通常会先看两个指标:checkpoint.duration 和 numberOfFailedCheckpoints。如果持续时间突然变长,很可能是反压导致Barrier传播变慢。如果频繁失败,检查一下状态后端是不是满了,或者远程存储的IO是不是瓶颈。
最后,送你一个Checkpoint配置的参考模板:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 开启Checkpoint,间隔5秒
env.enableCheckpointing(5000);
// 精确一次语义
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 超时时间10分钟
env.getCheckpointConfig().setCheckpointTimeout(600000);
// 同时进行的Checkpoint数
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
// 两次Checkpoint的最小间隔
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
嗯,这一章的内容就到这里。记住,Flink的架构、时间、状态、Checkpoint,这四个东西是排查一切故障的基础。搞懂了它们,你再看日志、看监控,心里就有底了。