2. Flink运行时架构
要理解反压、吞吐与延迟的调优,必须先看清Flink作业在集群中“长什么样”。Flink采用经典的主从(Master-Worker)运行时架构:JobManager负责作业的调度与协调,TaskManager负责实际的任务执行,而Slot则是TaskManager内部最细粒度的资源单位。作业提交后,逻辑上的算子图(Operator Graph)会被翻译成物理执行图(Execution Graph),最终以Task和SubTask的形式落到各个Slot上运行。这一节我们就把这条“从代码到运行”的链路拆开讲清楚。
一、核心组件:JobManager与TaskManager
JobManager是集群的“大脑”,它内部包含三个关键角色:Dispatcher负责接收客户端提交的作业并提供REST接口;ResourceManager负责Slot资源的申请、分配与回收;JobMaster则是单个作业的最高管理者,负责将Execution Graph调度到TaskManager上,并协调Checkpoint、故障恢复等。一个JobManager可以同时管理多个作业(Session模式下),每个作业对应一个独立的JobMaster。
TaskManager是集群的“肌肉”,是真正执行Task的工作节点。每个TaskManager是一个JVM进程,内部通过Slot来划分可用的计算资源。TaskManager会向ResourceManager注册自己的Slot,JobMaster再向ResourceManager申请Slot来部署Task。TaskManager之间通过Netty进行数据交换,这也是反压信号传播的物理通道。
二、Slot、Task与SubTask:资源模型的三层关系
很多初学者会把Slot、Task、SubTask混为一谈,其实它们描述的是不同层次的概念:
- Slot:TaskManager上的资源子集,代表固定的内存与计算份额(Flink 1.11后Slot主要隔离内存,CPU不再严格隔离)。一个TaskManager有多少个Slot,由
taskmanager.numberOfTaskSlots决定。 - Task:一个算子(或算子链)在一个并发实例上的执行单元,是调度的基本单位。
- SubTask:Task在并行度下的具体实例。并行度为4的Map算子,会产生4个SubTask,每个SubTask就是一个Task实例。
默认情况下,Flink允许Slot共享(Slot Sharing):同一个作业中不同算子的SubTask可以运行在同一个Slot里。这样做的价值在于,一个作业的并行度等于其最大算子并行度,而不是所有算子并行度之和,极大降低了资源需求。同时,同一Slot内的SubTask共享TCP连接,能减少网络开销。
# flink-conf.yaml 中与资源模型相关的核心参数
taskmanager.numberOfTaskSlots: 4
parallelism.default: 4
taskmanager.memory.process.size: 4096m
slot.sharing.group: default # 通过 SlotSharingGroup 控制共享范围
三、Operator Chain:算子链的合并逻辑
Flink会把满足条件的相邻算子合并成一个算子链(Operator Chain),合并后的整条链作为一个Task运行在同一个线程中。合并的条件主要有两个:上下游算子并行度一致,且数据分发方式为Forward(一对一,非keyBy、非rebalance)。
算子链的意义非常直接:数据在链内以方法调用的方式传递,不经过序列化和网络,避免了线程切换与缓冲开销,从而显著提升吞吐、降低延迟。但反过来,如果一条链过长,某个算子成为瓶颈,整条链都会受影响,反压也会在链内直接传导。因此调优时经常需要disableChaining()或startNewChain()来打断不合理的链。
DataStream<String> stream = env.addSource(new KafkaSource())
.map(new ParseMap()) // 与source并行度一致且forward,会合并
.keyBy(r -> r.userId) // keyBy引入shuffle,链在此断开
.window(TumblingEventTimeWindows.of(Time.seconds(10)))
.aggregate(new CountAgg())
.disableChaining(); // 强制该算子单独成链,便于定位瓶颈
四、一张图看清运行时结构
下面这张图把JobManager、TaskManager、Slot、Task与Operator Chain的层级关系放在一起,帮助你建立空间感:
五、案例:并行度与Slot配置不匹配的复盘
某团队提交作业时设置了parallelism.default: 8,但TaskManager只配置了taskmanager.numberOfTaskSlots: 2,且集群只有一台TaskManager。作业提交后一直处于CREATED状态,日志中反复出现:
org.apache.flink.runtime.jobmanager.scheduler.NoResourceAvailableException:
Could not allocate all requires slots within timeout of 300000 ms.
Slots required: 8, slots allocated: 2
复盘结论:Slot共享虽然能降低需求,但作业的最大并行度仍受限于集群总Slot数。该作业有8个并行度,而集群只有2个Slot,无法满足。解决方案有两种:一是增加TaskManager或提高numberOfTaskSlots;二是降低作业并行度。这里也引出一个常见误区——Slot数不等于CPU核数,一个Slot可以承载多个SubTask,但Slot数量决定了作业能同时运行的Task上限。
六、常见误区清单
| 误区 | 正确理解 |
|---|---|
| Slot就是CPU核 | Slot是资源子集,主要隔离内存,CPU不严格隔离 |
| 并行度等于Slot数 | 并行度是算子实例数,Slot是承载Task的资源单位,二者可不等 |
| 算子链越长越好 | 链过长会掩盖瓶颈,反压传导快,必要时需打断链 |
| JobManager负责数据处理 | JobManager只做调度协调,数据全在TaskManager处理 |
| Slot共享会降低性能 | 合理共享可减少网络开销和资源占用,是Flink的默认优化 |