Flink架构深度剖析:JobManager与TaskManager
前言
上一篇我们成功跑起了第一个 Flink 程序,但你有没有想过:当你点击"运行"后,代码是怎么被执行的?数据是怎么在多个节点之间流动的?
这篇文章我们就来揭开 Flink 的"底裤"------深入剖析 Flink 的架构设计。理解了架构,你才能知道程序为什么慢、哪里出了问题、怎么调优。
放心,我会用大白话 + 图解的方式来讲,保证你看完能说清楚 Flink 的架构。
🏠个人主页:你的主页
目录
- 一、Flink架构全景图
- 二、JobManager详解
- 三、TaskManager详解
- 四、作业提交与执行流程
- 五、Task与SubTask
- 六、算子链与任务槽
- 七、并行度的理解与配置
- 八、高可用架构
- 九、总结
一、Flink架构全景图
Flink 采用经典的 主从架构(Master-Slave),和 Hadoop、Spark 类似。先看一张全景图:
┌─────────────────────────────────────────────────────────────────────────┐
│ Flink 集群架构 │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ JobManager(主节点) │ │
│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
│ │ │ Dispatcher │ │ResourceManager│ │ JobMaster │ │ │
│ │ │ 接收作业提交 │ │ 资源管理 │ │ 作业调度执行 │ │ │
│ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 分配任务 │
│ ↓ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ TaskManager 1 │ │ TaskManager 2 │ │ TaskManager 3 │ │
│ │ ┌────┐ ┌────┐ │ │ ┌────┐ ┌────┐ │ │ ┌────┐ ┌────┐ │ │
│ │ │Slot│ │Slot│ │ │ │Slot│ │Slot│ │ │ │Slot│ │Slot│ │ │
│ │ └────┘ └────┘ │ │ └────┘ └────┘ │ │ └────┘ └────┘ │ │
│ │ 执行任务 │ │ 执行任务 │ │ 执行任务 │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │ │ │ │
│ └──────────────────────┴──────────────────────┘ │
│ 数据交换 │
└─────────────────────────────────────────────────────────────────────────┘
一句话概括:
- JobManager = 老板,负责接活、分配任务、监督进度
- TaskManager = 员工,负责干活、汇报状态
二、JobManager详解
JobManager 是 Flink 集群的大脑,负责整个作业的管理和协调。它由三个核心组件构成:
2.1 三大核心组件
┌─────────────────────────────────────────────────────────────────┐
│ JobManager │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Dispatcher │ │
│ │ • 提供 REST 接口,接收客户端提交的作业 │ │
│ │ • 为每个作业启动一个 JobMaster │ │
│ │ • 负责 Flink WebUI 展示 │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ ResourceManager │ │
│ │ • 管理集群资源(TaskManager 的 Slot) │ │
│ │ • 负责 TaskManager 的注册与心跳 │ │
│ │ • 与外部资源管理器对接(YARN/K8s/Mesos) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ JobMaster │ │
│ │ • 每个作业一个 JobMaster │ │
│ │ • 将 JobGraph 转换为 ExecutionGraph │ │
│ │ • 调度 Task 到 TaskManager 执行 │ │
│ │ • 协调 Checkpoint │ │
│ └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
2.2 用大白话解释
打个比方,JobManager 就像一个项目管理部门:
| 组件 | 角色类比 | 职责 |
|---|---|---|
| Dispatcher | 前台接待 | 接收客户需求(作业),分配给项目经理 |
| ResourceManager | HR部门 | 管理员工(TaskManager),分配工位(Slot) |
| JobMaster | 项目经理 | 负责具体项目,拆解任务、分配工作、跟进进度 |
2.3 JobManager 的核心职责
- 接收作业提交:通过 REST API 或命令行接收用户提交的 Jar 包
- 生成执行计划:将用户代码转换为可执行的任务图
- 调度任务:将任务分配到具体的 TaskManager 执行
- 协调 Checkpoint:触发检查点,协调各 Task 进行状态快照
- 故障恢复:当 Task 失败时,重新调度执行
三、TaskManager详解
TaskManager 是 Flink 集群的劳动力,负责实际执行计算任务。
3.1 TaskManager 结构
┌─────────────────────────────────────────────────────────────────┐
│ TaskManager │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Task Slot 1 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ SubTask │ │ SubTask │ │ SubTask │ ← 同一 Slot 可 │ │
│ │ │ (Source)│→ │ (Map) │→ │ (Sink) │ 运行多个 SubTask │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Task Slot 2 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ SubTask │→ │ SubTask │→ │ SubTask │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ Network Buffer │ │ Memory Pool │ │
│ │ 网络数据缓冲 │ │ 内存管理 │ │
│ └──────────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
3.2 什么是 Task Slot?
Slot(任务槽) 是 TaskManager 中资源分配的基本单位。你可以把它理解为"工位":
- 一个 TaskManager 可以有多个 Slot
- 每个 Slot 拥有固定的内存资源
- 不同 Slot 之间内存隔离,CPU 共享
默认配置:每个 TaskManager 有 1 个 Slot。生产环境通常配置 2-4 个。
yaml
# flink-conf.yaml
taskmanager.numberOfTaskSlots: 2
3.3 TaskManager 的核心职责
- 执行任务:运行 JobManager 分配下来的 SubTask
- 数据交换:与其他 TaskManager 交换数据(Shuffle)
- 状态管理:维护本地状态,参与 Checkpoint
- 心跳汇报:定期向 JobManager 汇报自身状态
3.4 TaskManager vs TaskSlot vs Task
这三个概念很容易混淆,我们来理清楚:
| 概念 | 是什么 | 类比 |
|---|---|---|
| TaskManager | 一个 JVM 进程 | 一个员工 |
| Task Slot | TM 中的资源单位 | 员工的工位 |
| Task | 一个算子的并行实例 | 一项具体工作 |
| SubTask | Task 的一个并行度实例 | 工作的一个子任务 |
举例:
- 一个 Flink 集群有 3 个 TaskManager
- 每个 TaskManager 有 2 个 Slot
- 那么集群总共有 6 个 Slot,最多可以同时执行 6 个并行任务
四、作业提交与执行流程
当你在 IDEA 点击运行,或者用 flink run 提交作业时,背后发生了什么?
4.1 完整流程图
┌──────────┐ ┌──────────────────────────────────────────────────────┐
│ Client │ │ JobManager │
│ │ │ │
│ 用户代码 │ │ Dispatcher ResourceManager JobMaster │
└────┬─────┘ └──────┬──────────────┬──────────────────┬─────────────┘
│ │ │ │
│ ① 提交 JobGraph │ │ │
│─────────────────→│ │ │
│ │ │ │
│ │ ② 启动 JobMaster │
│ │────────────────────────────────→│
│ │ │ │
│ │ │ ③ 申请 Slot │
│ │ │←──────────────────│
│ │ │ │
│ │ │ ④ 分配 Slot │
│ │ │──────────────────→│
│ │ │ │
│ │ │ │ ⑤ 部署 Task
│ │ │ │─────────┐
│ │ │ │ │
│ │ │ │ ↓
│ │ │ ┌────────────────────┐
│ │ │ │ TaskManager │
│ │ │ │ 执行 Task │
│ │ │ └────────────────────┘
4.2 流程详解
第一步:客户端提交作业
java
// 用户代码
env.execute("My Flink Job");
当调用 execute() 时,客户端会:
- 将用户代码编译成 StreamGraph(流图)
- 优化成 JobGraph(作业图)
- 通过 REST API 提交给 Dispatcher
第二步:Dispatcher 接收作业
Dispatcher 收到 JobGraph 后,为这个作业启动一个专属的 JobMaster。
第三步:JobMaster 申请资源
JobMaster 分析 JobGraph,计算需要多少个 Slot,然后向 ResourceManager 申请。
第四步:ResourceManager 分配 Slot
ResourceManager 检查有哪些 TaskManager 有空闲 Slot,分配给 JobMaster。
如果 Slot 不足:
- Standalone 模式:等待或报错
- YARN/K8s 模式:动态申请新的 TaskManager
第五步:部署 Task 执行
JobMaster 将 Task 部署到分配好的 Slot 上,TaskManager 开始执行。
4.3 三种图的转换
用户代码在提交过程中,会经历三次转换:
用户代码
│
│ 客户端
↓
StreamGraph(流图)
│
│ 客户端优化
↓
JobGraph(作业图)
│
│ JobMaster
↓
ExecutionGraph(执行图)
│
│ 调度执行
↓
物理执行
| 图类型 | 生成位置 | 特点 |
|---|---|---|
| StreamGraph | 客户端 | 最原始的逻辑图,一个算子一个节点 |
| JobGraph | 客户端 | 优化后的图,可合并的算子会chain在一起 |
| ExecutionGraph | JobMaster | 并行化后的图,每个节点按并行度展开 |
五、Task与SubTask
5.1 什么是 Task?
Task 是 Flink 执行的基本单位。但这里的 Task 不是指单个算子,而是算子链(Operator Chain)。
Flink 会把可以合并的算子串成一个 Task,减少数据传输开销。
5.2 什么是 SubTask?
SubTask 是 Task 的并行实例。如果一个 Task 的并行度是 3,那么它会有 3 个 SubTask。
┌────────────────────────────────────────────────────────────────┐
│ 一个 Task(算子链) │
│ │
│ Source → Map → Filter (这三个算子被合并成一个 Task) │
│ │
│ 并行度 = 3 │
│ │
│ ┌─────────────────┐ │
│ │ SubTask 0 │ ← 处理 partition 0 的数据 │
│ │ Source→Map→Filter │
│ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ SubTask 1 │ ← 处理 partition 1 的数据 │
│ │ Source→Map→Filter │
│ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ SubTask 2 │ ← 处理 partition 2 的数据 │
│ │ Source→Map→Filter │
│ └─────────────────┘ │
└────────────────────────────────────────────────────────────────┘
5.3 Task 数量计算
假设有如下作业:
java
source.setParallelism(2)
.map(...).setParallelism(2)
.keyBy(...)
.reduce(...).setParallelism(3)
.sink(...).setParallelism(3);
假设 source → map 被 chain 成一个 Task,reduce → sink 被 chain 成一个 Task:
- Task 1(source-map):2 个 SubTask
- Task 2(reduce-sink):3 个 SubTask
- 总共需要 5 个 Slot
六、算子链与任务槽
6.1 什么是算子链(Operator Chain)?
算子链 是 Flink 的一个重要优化。它把多个算子合并在一起,在同一个线程中执行,避免了:
-
线程切换开销
-
数据序列化/反序列化开销
-
网络传输开销
优化前:
┌────────┐ 网络 ┌────────┐ 网络 ┌────────┐
│ Source │ ────────→ │ Map │ ────────→ │ Filter │
└────────┘ └────────┘ └────────┘
↓ ↓ ↓
线程1 线程2 线程3优化后(算子链):
┌──────────────────────────────────────────┐
│ Source → Map → Filter │ ← 同一线程执行
│ 算子链 │
└──────────────────────────────────────────┘
↓
线程1
6.2 算子链的条件
不是所有算子都能被 chain 在一起,需要满足以下条件:
- 上下游并行度相同
- 数据传输方式是 Forward(一对一传输,非 shuffle)
- 在同一个 SlotSharingGroup 中
- 没有被用户禁用 chain
6.3 手动控制算子链
有时候你可能需要手动控制算子链的行为:
java
// 禁用当前算子与下游的 chain
dataStream.map(...).disableChaining();
// 从当前算子开始一个新的 chain
dataStream.map(...).startNewChain();
// 全局禁用算子链
env.disableOperatorChaining();
什么时候需要手动控制?
- 某个算子特别重(如调用外部 API),需要单独监控
- 排查性能问题,想看每个算子的耗时
6.4 Slot 共享(Slot Sharing)
默认情况下,Flink 允许不同 Task 的 SubTask 共享同一个 Slot。
┌────────────────────────────────────────────────────────┐
│ Slot │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Source │ │ Map │ │ Sink │ │
│ │ SubTask0│ │ SubTask0│ │ SubTask0│ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ ← 三个不同 Task 的 SubTask 共享一个 Slot │
└────────────────────────────────────────────────────────┘
好处:
- 充分利用 Slot 资源
- 减少需要的 Slot 数量
- 同一 Slot 内数据传输不走网络
计算公式:
需要的 Slot 数 = max(各算子的并行度)
例如 Source 并行度 2,Map 并行度 3,Sink 并行度 3,那么需要 3 个 Slot(而不是 2+3+3=8)。
七、并行度的理解与配置
7.1 什么是并行度?
并行度(Parallelism) 就是一个算子同时有多少个实例在并行执行。
并行度 = 1 并行度 = 3
┌─────────┐ ┌─────────┐
│ Map │ │ Map 0 │
│ │ ← 单实例 ├─────────┤
└─────────┘ │ Map 1 │ ← 三个实例并行
├─────────┤
│ Map 2 │
└─────────┘
7.2 并行度的四种设置方式
优先级从高到低:
java
// 1. 算子级别(最高优先级)
dataStream.map(...).setParallelism(2);
// 2. 执行环境级别
env.setParallelism(3);
// 3. 提交时指定
// flink run -p 4 xxx.jar
// 4. 配置文件(最低优先级)
// flink-conf.yaml: parallelism.default: 1
7.3 如何设置合理的并行度?
经验法则:
| 场景 | 并行度建议 |
|---|---|
| 开发测试 | 1-2,方便调试 |
| 生产环境 | 等于或略小于可用 Slot 数 |
| Kafka Source | 等于 Kafka 分区数 |
| CPU 密集型 | 接近 CPU 核心数 |
| IO 密集型 | 可以超过 CPU 核心数 |
注意:Source 的并行度不能超过 Kafka 分区数,否则多余的 SubTask 会空转。
7.4 并行度实例
java
// 假设 Kafka 有 6 个分区,集群有 12 个 Slot
env.setParallelism(6); // 全局默认并行度
KafkaSource<String> source = KafkaSource.<String>builder()
// ...
.build();
env.fromSource(source, WatermarkStrategy.noWatermarks(), "Kafka Source")
.setParallelism(6) // Source 并行度 = Kafka 分区数
.map(...)
.setParallelism(12) // Map 可以更高
.keyBy(...)
.reduce(...)
.setParallelism(6) // 聚合操作并行度适中
.addSink(...)
.setParallelism(6);
八、高可用架构
生产环境中,JobManager 是单点,一旦挂掉,整个作业就停了。Flink 支持 HA(High Availability) 模式。
8.1 Standalone HA
┌─────────────────────────────────────────────────────────────────┐
│ ZooKeeper 集群 │
│ (Leader 选举) │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ↓ ↓ ↓ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │JobManager │ │JobManager │ │JobManager │ │
│ │ (Leader) │ │ (Standby) │ │ (Standby) │ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ │
│ 元数据存储:HDFS / S3 / NFS │
└─────────────────────────────────────────────────────────────────┘
工作原理:
- 多个 JobManager 启动,通过 ZK 选举 Leader
- Leader 负责实际工作,Standby 待命
- 作业元数据(JobGraph、Checkpoint 路径)存储在共享存储
- Leader 挂掉后,Standby 接管,从共享存储恢复
8.2 YARN/K8s HA
在 YARN 或 K8s 上,HA 更简单:
- YARN:ApplicationMaster(JobManager)失败后,YARN 会自动重启
- K8s:通过 Deployment 配置副本数,Pod 失败自动重建
8.3 配置示例
yaml
# flink-conf.yaml
# 开启 HA
high-availability: zookeeper
high-availability.zookeeper.quorum: zk1:2181,zk2:2181,zk3:2181
high-availability.zookeeper.path.root: /flink
high-availability.storageDir: hdfs:///flink/ha/
high-availability.cluster-id: my-flink-cluster
九、总结
这篇文章我们深入了解了 Flink 的架构设计:
核心组件
| 组件 | 职责 | 类比 |
|---|---|---|
| JobManager | 作业管理、任务调度、协调 Checkpoint | 老板/项目经理 |
| TaskManager | 执行任务、数据交换、状态管理 | 员工 |
| Dispatcher | 接收作业、启动 JobMaster | 前台 |
| ResourceManager | 管理资源、分配 Slot | HR |
| JobMaster | 单个作业的调度执行 | 项目经理 |
核心概念
| 概念 | 说明 |
|---|---|
| Task Slot | TaskManager 中的资源单位,内存隔离 |
| Task | 算子链,执行的基本单位 |
| SubTask | Task 的并行实例 |
| 算子链 | 多个算子合并,减少开销 |
| 并行度 | 算子的并行实例数 |
| Slot 共享 | 不同 Task 可共享 Slot |
作业执行流程
- 客户端生成 JobGraph,提交给 Dispatcher
- Dispatcher 启动 JobMaster
- JobMaster 向 ResourceManager 申请 Slot
- ResourceManager 分配 Slot
- JobMaster 部署 Task 到 TaskManager 执行
下一篇文章,我们将学习 Flink 的编程模型:DataStream 与 DataSet,深入理解流批一体的 API 设计。
热门专栏推荐
- Agent小册
- Java基础合集
- Python基础合集
- Go基础合集
- 大数据合集
- 前端小册
- 数据库合集
- Redis 合集
- Spring 全家桶
- 微服务全家桶
- 数据结构与算法合集
- 设计模式小册
- Ai工具小册
等等等还有许多优秀的合集在主页等着大家的光顾,感谢大家的支持
文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论😊
希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读🙏
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🌟