前面讲了 Flink Task 调度任务的完整链路(创建 Execution → 调度 → 申请 Slot → 部署 Task → 运行执行 → 完成或失败),这篇聚焦 Flink Task 执行的源码深度剖析。Task 部署到 TaskManager 后,Task 线程如何启动?OperatorChain 如何创建?算子的 open()/processElement()/close() 如何调用?数据如何在算子间传递?RecordWriter 和 RecordReader 如何工作?反压机制如何实现?这篇从源码级别跟踪 Flink Task 执行的完整链路。
一、Task 执行整体架构
下面这张图是 Task 执行整体架构,包括 TaskManager 内部组件、内存与环境管理、外部交互和执行流程。

Flink Task 执行涉及三个核心部分:
- TaskManager(工作节点):运行 TaskExecutor、Task、OperatorChain、StreamTask、RecordWriter、RecordReader、InputGate、ResultPartition。TaskExecutor 接收 submitTask 请求,创建 Task 实例并启动执行;Task 是具体的执行单元,实现 Runnable 接口;OperatorChain 串联多个算子,数据在算子间通过内存直接传递;RecordWriter 输出数据到 ResultPartition,RecordReader 从 InputGate 读取上游数据。
- 内存与环境管理:运行 MemoryManager(内存管理器,管理堆内堆外内存)、NetworkEnvironment(网络环境,管理网络缓冲和连接)、StateBackend(状态后端,管理状态存储和恢复)、IOManager(IO 管理器,管理磁盘 IO)、ShuffleEnvironment(Shuffle 环境,管理数据 Shuffle 传输)。这些组件为 Task 执行提供内存、网络、状态、IO 等基础设施。
- 外部交互:Task 执行过程中与 JobMaster(提交 Task,接收状态更新)、上游 TaskManager(通过网络发送数据)、下游 TaskManager(通过网络接收数据)、Source 外部系统(Kafka/HDFS/MySQL 等数据源)、Sink 外部系统(Kafka/ES/MySQL 等数据汇)、Checkpoint 存储(HDFS/S3 等持久化存储)交互。
Task 执行核心流程5步:启动 Task 线程 → 创建 OperatorChain → open 算子 → processElement 处理数据 → close 释放资源。
二、Task 执行完整流程
下面这张图是 Task 执行完整流程,包括6步执行流程、方法调用链、核心类和常见问题。

Task 执行分为6步:
第1步:启动 Task 线程。TaskExecutor 收到 JobMaster 的 submitTask 请求后,首先验证作业 ID 和 Slot 是否有效,然后创建 Task 实例,启动 Task 线程。Task 实现 Runnable 接口,run() 方法是核心执行逻辑。Task 线程启动后,状态从 DEPLOYING 转为 RUNNING,通知 JobMaster Task 已启动。
第2步:创建 OperatorChain。Task 线程启动后,创建 OperatorChain(算子链)。OperatorChain 包含链内所有算子,创建 RecordWriter(输出)和 RecordReader(输入),初始化 InputGate 和 ResultPartition。OperatorChain 将多个算子串联,数据在算子间通过内存直接传递,减少序列化和网络开销。算子链优化是 Flink 性能优化的重要手段,将可以串联的算子(如 map→filter→map)合并到一个 Task 中执行。
第3步:调用 open() 初始化算子。OperatorChain 创建完成后,依次调用链内所有算子的 open() 方法。open() 方法用于初始化算子状态,如创建连接池、初始化状态后端、加载广播变量、注册定时器等。open() 方法在处理数据之前调用,确保算子准备就绪。RichFunction 接口提供 open() 和 close() 生命周期方法,普通 Function 没有生命周期方法。
第4步:processElement() 处理数据。算子初始化完成后,StreamTask 进入数据处理循环。RecordReader 从 InputGate 读取上游数据,反序列化为记录,调用第一个算子的 processElement() 方法。数据在算子链内依次传递,每个算子处理后输出到下一个算子。链尾算子通过 RecordWriter 将数据写入 ResultPartition,通过网络发送到下游。处理循环持续直到所有数据处理完成或任务被取消。
第5步:调用 close() 释放资源。所有数据处理完成后,依次调用链内所有算子的 close() 方法。close() 方法用于释放算子资源,如关闭连接池、刷新状态、清理定时器、关闭文件句柄等。close() 方法在处理数据之后调用,确保资源正确释放。close() 方法调用完成后,OperatorChain 释放 RecordWriter 和 RecordReader,关闭 InputGate 和 ResultPartition。
第6步:完成释放,通知 JobMaster。Task 执行完成后,状态从 RUNNING 转为 FINISHED,释放分配的 Slot,通知 JobMaster Task 已完成。JobMaster 收到完成通知后,标记对应 Execution 为 FINISHED,通知下游 Execution 可以调度。如果 Task 执行失败,状态转为 FAILED,触发故障恢复策略,重新调度受影响的 Task。
三、核心类源码剖析
3.1 Task 任务实例
java
public class Task implements Runnable {
private final JobID jobId;
private final JobInformation jobInformation;
private final TaskInformation taskInformation;
private final ExecutionAttemptID executionId;
private final AllocationID allocationId;
private volatile Thread executingThread;
private volatile ExecutionState executionState;
public void startTaskThread() {
executingThread = new Thread(this, "Task Thread");
executingThread.start();
}
@Override
public void run() {
try {
// 1. 状态转换:DEPLOYING → RUNNING
executionState = ExecutionState.RUNNING;
notifyJobManager(ExecutionState.RUNNING);
// 2. 创建算子链 OperatorChain
OperatorChain operatorChain = new OperatorChain(
taskInformation,
jobInformation,
executionId,
memoryManager,
ioManager,
networkEnvironment,
stateBackend,
checkpointStorage,
checkpointResponder,
kvStateService,
shuffleEnvironment);
// 3. 调用所有算子的 open() 方法
operatorChain.openOperators();
// 4. 处理数据,调用 processElement()
operatorChain.processInputs();
// 5. 调用所有算子的 close() 方法
operatorChain.closeOperators();
// 6. 状态转换:RUNNING → FINISHED
executionState = ExecutionState.FINISHED;
notifyJobManager(ExecutionState.FINISHED);
} catch (Throwable t) {
// 处理异常,状态转换:RUNNING → FAILED
executionState = ExecutionState.FAILED;
notifyJobManager(ExecutionState.FAILED, t);
} finally {
// 释放资源
cleanup();
}
}
public void cancelExecution() {
if (executionState == ExecutionState.RUNNING) {
executionState = ExecutionState.CANCELING;
executingThread.interrupt();
}
}
private void notifyJobManager(ExecutionState state, Throwable... cause) {
TaskExecutionState taskState = new TaskExecutionState(executionId, state, cause);
taskManagerActions.updateTaskExecutionState(taskState);
}
private void cleanup() {
// 释放 Slot,关闭资源
releaseSlot();
closeResources();
}
}
Task 是具体的任务执行单元,实现 Runnable 接口。run() 方法是核心执行逻辑:首先将状态从 DEPLOYING 转为 RUNNING,通知 JobMaster;然后创建 OperatorChain 算子链;接着调用所有算子的 open() 方法初始化;然后处理数据,调用 processElement();处理完成后调用 close() 方法释放资源;最后将状态转为 FINISHED,通知 JobMaster。如果执行过程中出现异常,catch 块捕获异常,状态转为 FAILED,通知 JobMaster 触发故障恢复。cancelExecution() 方法取消正在执行的 Task,通过 interrupt() 中断 Task 线程。
3.2 TaskExecutor 任务执行器
java
public class TaskExecutor implements TaskExecutorGateway {
private final Map<ExecutionAttemptID, Task> tasks;
private final SlotTable slotTable;
private final MemoryManager memoryManager;
@Override
public CompletableFuture<Acknowledge> submitTask(
TaskDeploymentDescriptor tdd,
JobMasterId jobMasterId,
Time timeout) {
// 1. 验证作业 ID 和 Slot
JobID jobId = tdd.getJobId();
AllocationID allocationId = tdd.getAllocationId();
if (!slotTable.tryMarkSlotActive(jobId, allocationId)) {
throw new TaskSubmissionException("Slot not found or not active");
}
// 2. 创建 Task 实例
Task task = new Task(
jobId,
tdd.getJobInformation(),
tdd.getTaskInformation(),
executionAttemptId,
allocationId,
taskManagerActions,
inputSplitProvider,
checkpointResponder,
memoryManager,
ioManager,
networkEnvironment,
libraryCacheManager,
fileCache,
shuffleEnvironment,
kvStateService,
broadcastVariableManager,
taskEventDispatcher,
externalResourceInfoProvider,
fatalErrorHandler);
// 3. 启动 Task 线程
tasks.put(executionAttemptId, task);
task.startTaskThread();
return CompletableFuture.completedFuture(Acknowledge.get());
}
@Override
public void updateTaskExecutionState(TaskExecutionState taskExecutionState) {
ExecutionAttemptID executionId = taskExecutionState.getID();
Task task = tasks.get(executionId);
if (task != null) {
task.notifyUpdate(taskExecutionState);
}
}
@Override
public CompletableFuture<Acknowledge> cancelTask(
ExecutionAttemptID executionAttemptId,
Time timeout) {
Task task = tasks.get(executionAttemptId);
if (task != null) {
task.cancelExecution();
}
return CompletableFuture.completedFuture(Acknowledge.get());
}
}
TaskExecutor 是 Task 执行器,运行在 TaskManager 上。submitTask() 方法接收 JobMaster 的 submitTask 请求,首先验证作业 ID 和 Slot 是否有效,然后创建 Task 实例,启动 Task 线程执行。updateTaskExecutionState() 方法接收 Task 状态更新,通知 JobMaster。cancelTask() 方法取消正在执行的 Task。TaskExecutor 管理所有正在运行的 Task,负责 Task 的生命周期管理。
3.3 OperatorChain 算子链
java
public class OperatorChain {
private final StreamOperator<?>[] allOperators;
private final RecordWriter<?>[] recordWriters;
private final RecordReader<?>[] recordReaders;
private final InputGate[] inputGates;
private final ResultPartition[] resultPartitions;
public OperatorChain(...) {
// 1. 创建 InputGate 和 ResultPartition
this.inputGates = createInputGates();
this.resultPartitions = createResultPartitions();
// 2. 创建 RecordReader 和 RecordWriter
this.recordReaders = createRecordReaders(inputGates);
this.recordWriters = createRecordWriters(resultPartitions);
// 3. 创建算子实例(从链尾到链头)
this.allOperators = createOperators();
}
public void openOperators() throws Exception {
// 依次调用所有算子的 open() 方法
for (StreamOperator<?> operator : allOperators) {
operator.open();
}
}
public void processInputs() throws Exception {
// 数据处理循环
while (isRunning) {
// 从 RecordReader 读取下一条数据
StreamRecord record = recordReaders[0].getNext();
if (record == null) {
break; // 所有数据处理完成
}
// 调用第一个算子的 processElement()
// 数据在算子链内依次传递
allOperators[0].processElement(record);
}
}
public void closeOperators() throws Exception {
// 依次调用所有算子的 close() 方法
for (StreamOperator<?> operator : allOperators) {
operator.close();
}
}
public void releaseResources() {
// 释放 RecordWriter 和 RecordReader
for (RecordWriter<?> writer : recordWriters) {
writer.close();
}
for (RecordReader<?> reader : recordReaders) {
reader.close();
}
// 关闭 InputGate 和 ResultPartition
for (InputGate gate : inputGates) {
gate.close();
}
for (ResultPartition partition : resultPartitions) {
partition.close();
}
}
}
OperatorChain 是算子链,将多个算子串联到一个 Task 中执行。构造方法创建 InputGate 和 ResultPartition,创建 RecordReader 和 RecordWriter,创建算子实例。openOperators() 方法依次调用所有算子的 open() 方法初始化。processInputs() 方法是数据处理循环,从 RecordReader 读取数据,调用第一个算子的 processElement(),数据在算子链内依次传递。closeOperators() 方法依次调用所有算子的 close() 方法释放资源。releaseResources() 方法释放 RecordWriter、RecordReader、InputGate、ResultPartition 等资源。
3.4 RecordWriter 记录写入器
java
public class RecordWriter<T> {
private final ResultPartitionWriter targetPartition;
private final ChannelSelector<T> channelSelector;
private final Serializer<T> serializer;
private final int numberOfChannels;
public void emit(T record) throws IOException {
// 1. 选择目标通道
int channel = channelSelector.selectChannel(record);
// 2. 序列化记录
byte[] serialized = serializer.serialize(record);
// 3. 写入 ResultPartition
targetPartition.writeBuffer(serialized, channel);
}
public void broadcastEmit(T record) throws IOException {
// 广播发送到所有通道
byte[] serialized = serializer.serialize(record);
for (int channel = 0; channel < numberOfChannels; channel++) {
targetPartition.writeBuffer(serialized, channel);
}
}
public void flush() throws IOException {
// 刷新缓冲,发送数据
targetPartition.flush();
}
public void close() throws IOException {
// 关闭写入器
flush();
targetPartition.close();
}
}
RecordWriter 是记录写入器,负责将算子输出的数据写入 ResultPartition。emit() 方法首先通过 ChannelSelector 选择目标通道(keyBy 时根据 key 的 hash 值选择通道),然后序列化记录,最后写入 ResultPartition。broadcastEmit() 方法广播发送到所有通道(broadcast 算子使用)。flush() 方法刷新缓冲,发送数据。close() 方法关闭写入器。RecordWriter 是算子输出的出口,数据通过 RecordWriter 写入 ResultPartition,再通过网络发送到下游 TaskManager。
四、数据传输与反压机制
下面这张图是数据传输与反压机制,包括 Pipelined vs Blocking 对比、网络缓冲机制、Credit-based 反压流程和配置最佳实践。

Pipelined vs Blocking 数据传输
Flink Task 执行中的数据传输有两种模式:
- Pipelined(流水线传输):数据一条一条实时传输,上游处理完立即发送给下游。适用于流处理作业,低延迟要求,所有 Task 同时运行。需要所有 Task 同时运行,Slot 需求大,使用 Eager 调度。延迟低,毫秒级,数据实时流转。反压机制使用 Credit-based 反压,下游缓冲满时上游暂停发送。
- Blocking(阻塞传输):上游全部处理完成后,批量发送给下游,数据落盘。适用于批处理作业,高吞吐要求,分阶段执行。只需要当前阶段的 Task,Slot 可复用,使用 Lazy 调度。延迟高,分钟级,需要等待上游完成。天然反压,上游完成后下游才开始,无需网络反压。
流处理作业默认使用 Pipelined 传输,批处理作业默认使用 Blocking 传输。可以通过 execution.shuffle-mode 配置指定 Shuffle 模式。
网络缓冲机制
网络缓冲机制分为5层:RecordWriter(记录写入器,序列化数据)→ ResultPartition(结果分区,管理输出缓冲)→ Netty Server/Client(网络服务,发送/接收数据)→ InputGate(输入网关,管理输入缓冲)→ RecordReader(记录读取器,反序列化数据)。数据从上游算子输出,经过 RecordWriter 序列化,写入 ResultPartition 缓冲,通过 Netty 网络发送到下游,下游 InputGate 接收数据,RecordReader 反序列化,输入到下游算子。
Credit-based 反压机制
Credit-based 反压是 Flink 的流控机制,流程5步:
- 下游缓冲满:InputGate 缓冲已满,无法接收更多数据
- 停止发送 Credit:下游停止向上游发送 Credit(信用量)
- 上游缓冲满:ResultPartition 缓冲已满,无法写入更多数据
- 上游暂停处理:RecordWriter 阻塞,算子暂停处理数据
- 反压传播:反压向上游传播,直到 Source 算子暂停读取数据
Credit-based 反压的核心思想是:下游告诉上游自己还有多少缓冲空间(Credit),上游只发送不超过 Credit 的数据。当下游缓冲满时,停止发送 Credit,上游无法发送数据,ResultPartition 缓冲满后 RecordWriter 阻塞,算子暂停处理,反压向上游传播。这种机制可以有效防止数据丢失和 OOM,保证系统稳定性。
五、状态管理与 Checkpoint
Task 执行过程中,状态管理是核心功能之一。Flink 的状态分为两种:
- Keyed State(键控状态):按 key 分区的状态,每个 key 有独立的状态,如 ValueState、ListState、MapState、ReducingState、AggregatingState。Keyed State 只能在 keyBy 之后的算子中使用,状态按 key 自动分区和恢复。
- Operator State(算子状态):与算子实例绑定的状态,不按 key 分区,如 ListState、UnionListState、BroadcastState。Operator State 适用于 Source/Sink 等非 keyBy 算子,状态在并行度调整时重新分配。
状态后端(StateBackend)负责状态的存储和恢复:
- HashMapStateBackend:状态存储在 TaskManager 的堆内存中,读写快,但状态大小受内存限制,适合小状态场景。
- EmbeddedRocksDBStateBackend:状态存储在 RocksDB 中(本地磁盘),支持大状态,读写需要序列化/反序列化,适合大状态场景。
Checkpoint 是 Flink 的容错机制,定期将状态快照持久化到 Checkpoint 存储(HDFS/S3)。Task 执行过程中,JobManager 触发 Checkpoint,Source 算子注入 Checkpoint Barrier,Barrier 随数据在算子间传递,算子收到 Barrier 后快照状态,发送 Barrier 到下游。所有算子完成快照后,Checkpoint 完成。故障恢复时,从最近的 Checkpoint 恢复状态,保证 Exactly-Once 语义。
六、关键配置参数
yaml
# 网络内存占 TaskManager 总内存比例
taskmanager.memory.network.fraction: 0.1
# 每个通道独占缓冲数(Exclusive Buffers)
taskmanager.network.memory.buffers-per-channel: 2
# 每个 InputGate 浮动缓冲数(Floating Buffers)
taskmanager.network.memory.floating-buffers-per-gate: 8
# 网络缓冲大小
taskmanager.network.memory.buffer-size: 32KB
# 网络请求最大退避时间
taskmanager.network.request-backoff.max: 10s
# Shuffle 模式(pipelined/blocking/hybrid)
execution.shuffle-mode: pipelined
# Checkpoint 间隔
execution.checkpointing.interval: 1min
# 状态后端
state.backend: rocksdb
# 每个 TaskManager 的 Slot 数
taskmanager.numberOfTaskSlots: 1
# 默认并行度
parallelism.default: 1
关键配置说明:
- taskmanager.memory.network.fraction:网络内存占 TaskManager 总内存比例,默认 10%,大流量作业可调大到 15-20%。
- buffers-per-channel:每个通道独占缓冲数,默认 2,保证最低传输能力,高吞吐场景可调大。
- floating-buffers-per-gate:每个 InputGate 浮动缓冲数,默认 8,在通道间动态分配,高吞吐场景可调大。
- execution.shuffle-mode:Shuffle 模式,pipelined 流水线传输(流处理),blocking 阻塞传输(批处理),hybrid 混合模式。
- state.backend:状态后端,hashmap 堆内存(小状态),rocksdb 本地磁盘(大状态)。
七、常见问题与最佳实践
常见问题
-
open() 初始化失败
- 原因:连接池创建失败、状态后端初始化异常、资源不足、广播变量加载失败。
- 解决:检查外部系统连接,增加内存,查看 TM 日志,确认广播变量大小。
-
数据处理慢
- 原因:数据倾斜、算子逻辑复杂、反压、GC 频繁、状态访问慢。
- 解决:优化 keyBy 分区,简化算子逻辑,增加并行度,调优 GC,使用 RocksDB 状态后端。
-
close() 资源泄漏
- 原因:连接未关闭、文件句柄泄漏、状态未刷新、定时器未清理。
- 解决:在 close() 中释放所有资源,使用 try-with-resources,确保状态刷新。
-
Task 频繁失败
- 原因:代码异常、OOM、Checkpoint 失败、网络异常、反压导致超时。
- 解决:检查代码逻辑,增加内存,优化 Checkpoint,配置重启策略,调大超时。
-
反压严重
- 原因:下游算子处理慢、网络带宽不足、缓冲配置不当、数据倾斜。
- 解决:优化慢算子,增加网络内存,调整缓冲配置,解决数据倾斜。
-
状态过大
- 原因:状态未清理、TTL 未配置、状态后端选择不当、Checkpoint 过大。
- 解决:配置状态 TTL,使用 RocksDB 状态后端,增量 Checkpoint,清理无用状态。
最佳实践
-
使用算子链优化:将可以串联的算子(如 map→filter→map)合并到一个 Task 中执行,减少序列化和网络开销,提高性能。Flink 默认开启算子链优化,可通过 disableChaining() 禁用。
-
合理配置网络内存:大流量作业可调大 network.fraction 到 0.15-0.2,增加缓冲,减少反压。高吞吐场景增加 floating-buffers-per-gate,低延迟场景减少缓冲。
-
监控反压指标:关注 Web UI 反压指标(OK/LOW/HIGH),及时发现下游处理慢的算子。反压源头通常是慢算子,优化算子逻辑或增加并行度。
-
配置状态 TTL:为 Keyed State 配置 TTL(Time-To-Live),自动清理过期状态,防止状态无限增长。TTL 配置在 StateDescriptor 中,支持 processing time 和 event time。
-
选择合适的状态后端:小状态(<100MB)使用 HashMapStateBackend,读写快;大状态(>1GB)使用 EmbeddedRocksDBStateBackend,支持大状态,读写需要序列化。
-
开启增量 Checkpoint:大状态作业开启增量 Checkpoint,只快照变更的状态,减少 Checkpoint 时间和存储开销。RocksDB 状态后端支持增量 Checkpoint。
-
合理配置并行度:根据数据量和资源设置并行度,避免并行度过高导致调度开销大,或过低导致处理能力不足。Source 并行度受数据源分片数限制,可通过增加分片提高并行度。
八、总结
Flink Task 执行是一个6步流程:启动 Task 线程 → 创建 OperatorChain → 调用 open() 初始化算子 → processElement() 处理数据 → 调用 close() 释放资源 → 完成释放通知 JobMaster。核心类包括 Task(Runnable 实现,run 方法是核心执行逻辑)、TaskExecutor(Task 执行器,接收 submitTask,管理 Task 生命周期)、OperatorChain(算子链,串联多个算子,管理输入输出)、StreamTask(流任务,驱动数据处理循环)、RecordWriter(记录写入器,输出数据到 ResultPartition)、RecordReader(记录读取器,从 InputGate 读取数据)。
数据传输有两种模式:Pipelined(流水线传输,低延迟,适合流处理,Eager 调度)和 Blocking(阻塞传输,高吞吐,适合批处理,Lazy 调度)。网络缓冲机制分为5层:RecordWriter → ResultPartition → Netty Server/Client → InputGate → RecordReader。Credit-based 反压是 Flink 的流控机制,下游缓冲满时停止发送 Credit,上游 ResultPartition 缓冲满后 RecordWriter 阻塞,反压向上游传播直到 Source。
状态管理分为 Keyed State(按键分区)和 Operator State(算子实例绑定),状态后端有 HashMapStateBackend(堆内存,小状态)和 EmbeddedRocksDBStateBackend(本地磁盘,大状态)。Checkpoint 定期快照状态,故障恢复时从最近的 Checkpoint 恢复,保证 Exactly-Once 语义。
理解 Flink Task 执行的完整链路和核心类实现,有助于排查 open() 初始化失败、数据处理慢、close() 资源泄漏、Task 频繁失败、反压严重、状态过大等常见问题,为 Flink 作业的稳定运行提供保障。使用算子链优化、合理配置网络内存、监控反压指标、配置状态 TTL、选择合适的状态后端、开启增量 Checkpoint、合理配置并行度,可以显著提高作业的性能和稳定性。