第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混为一谈。

这里我画了一张架构图,帮你直观理解:

JobManager 调度 / Checkpoint协调 TaskManager 1 Slot 1 Slot 2 Slot 3 TaskManager 2 Slot 1 Slot 2 Kafka Source MySQL Sink 图1:Flink运行时架构(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恢复。

它的工作流程是这样的:

  1. JobManager向Source算子注入一个Barrier(屏障)。
  2. Barrier随着数据流往下游传播。
  3. 每个算子收到Barrier后,把当前状态快照保存到远程存储(如HDFS)。
  4. 所有算子都完成快照后,这个Checkpoint就算成功了。

这里有个关键点:Barrier对齐。如果一个算子有多个输入流,它必须等到所有输入流的Barrier都到齐,才能做快照。这期间,先到的数据会被缓存起来。

我的经验:排查Checkpoint失败时,我通常会先看两个指标:checkpoint.durationnumberOfFailedCheckpoints。如果持续时间突然变长,很可能是反压导致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,这四个东西是排查一切故障的基础。搞懂了它们,你再看日志、看监控,心里就有底了。

专注资料整理