前面讲了 TaskManager 的启动流程,这篇聚焦 Slot 注册的源码实现。你有没有想过,TaskManager 启动后,Slot 是如何注册到 ResourceManager 的?SlotReport 是如何创建和上报的?SlotManager 内部是如何管理这些 Slot 的?Slot 的四种状态(FREE/PENDING/ALLOCATED/RELEASED)是如何转换的?这篇从源码级别深度剖析 Slot 注册的完整链路。
一、注册 Slot 整体架构
下面这张图是注册 Slot 整体架构,包括四个核心组件、注册流程和 SlotReport 数据结构。

注册 Slot 涉及四个核心组件:
- TaskExecutor:运行在 TaskManager 内部,负责创建 SlotReport,向 ResourceManager 注册和上报心跳。内部包含 SlotTable(管理本地 Slot)、TaskManagerLocation(位置信息)、HeartbeatManager(心跳管理)。
- ResourceManager:集群资源管理器,接收 TaskManager 注册和 SlotReport。内部包含 ResourceManagerImpl(核心实现)、TaskManagerManager(管理 TM 注册和心跳)、ResourceManagerDriver(YARN/K8s 资源申请)。
- SlotManager:运行在 ResourceManager 内部,是 Slot 注册和管理的实际执行者。内部包含 SlotManagerImpl(核心实现)、SlotStatusSyncer(Slot 状态同步)、PendingSlotRequest(待处理请求)。
- TaskManagerSlot:Slot 实例,管理 Slot 状态和分配信息。内部包含 SlotState(状态枚举)、SlotStatusListener(状态监听器)。
注册 Slot 的核心流程分为5步:TaskManager 注册 → SlotReport 上报 → SlotManager 接收 → 创建 TaskManagerSlot → 心跳更新状态。
二、注册 Slot 完整流程
下面这张图是注册 Slot 完整流程,包括6步注册流程、核心类、方法调用链和常见问题。

注册 Slot 分为6步:
第1步:TaskManager 注册。TaskExecutor 启动后,通过 ResourceManagerGateway 向 ResourceManager 发送 registerTaskManager 请求。上报 TaskManager 资源信息(Slot 数、内存、CPU、网络端口),RM 分配 ResourceID,返回 RegistrationResponse 确认。
第2步:创建 SlotReport。TaskExecutor 从 SlotTable 中获取所有 Slot 的状态,创建 SlotReport。每个 SlotStatus 包含 slotID、resourceProfile、jobID、allocationID 四个字段。初始注册时所有 Slot 状态为 FREE,jobID 和 allocationID 为 null。
第3步:RM 转发 SlotReport。ResourceManager 收到 SlotReport 后,调用 slotManager.registerTaskManager() 将 TaskManager 和 SlotReport 转发给 SlotManager。同时将 TaskExecutorGateway 注册到 taskManagerConnections 中,用于后续向 TaskManager 发送 Slot 分配请求。
第4步:创建 TaskManagerSlot。SlotManager 遍历 SlotReport 中的每个 SlotStatus,为每个 Slot 创建 TaskManagerSlot 实例。TaskManagerSlot 初始状态为 FREE,记录 slotID、resourceProfile、taskManagerId 等信息。将所有 TaskManagerSlot 加入 taskManagerSlots 映射中管理。
第5步:注册 SlotStatusListener。SlotManager 为每个 TaskManagerSlot 注册 SlotStatusListener,监听 Slot 状态变化。当 Slot 状态从 FREE 变为 PENDING 或 ALLOCATED 时,监听器会通知 SlotManager 更新内部状态。同时启动 SlotStatusSyncer,定期同步 Slot 状态。
第6步:心跳更新 Slot 状态。TaskManager 启动心跳定时器,每 10 秒向 ResourceManager 发送心跳,心跳中携带最新的 SlotReport。ResourceManager 调用 slotManager.reportSlotStatus() 更新 Slot 状态。SlotManager 对比新旧 SlotReport,发现状态变化时更新 TaskManagerSlot 状态,触发 PendingSlotRequest 分配。
三、核心类源码剖析
3.1 TaskExecutor 注册逻辑
java
public class TaskExecutor extends TaskExecutorFencedRpcEndpoint {
private final SlotTable slotTable;
private final ResourceManagerAddress resourceManagerAddress;
@Override
protected void onStart() throws Exception {
super.onStart();
// 启动后立即尝试注册
tryRegisterTaskManager();
}
private void tryRegisterTaskManager() {
if (resourceManagerAddress != null) {
// 创建 SlotReport
SlotReport slotReport = slotTable.createSlotReport();
// 发送注册请求
resourceManagerGateway.registerTaskManager(
taskManagerLocation,
taskManagerConfiguration,
slotReport,
timeout)
.whenComplete((response, throwable) -> {
if (throwable != null) {
// 注册失败,重试
scheduleRegistrationRetry();
} else {
// 注册成功,启动心跳
startHeartbeat();
}
});
}
}
}
TaskExecutor 启动后在 onStart() 中立即尝试注册。创建 SlotReport 后发送 registerTaskManager 请求,注册成功后启动心跳,失败则定时重试。
3.2 SlotTable 创建 SlotReport
java
public class SlotTable {
private final Map<SlotID, Slot> slots;
private final TaskManagerLocation taskManagerLocation;
public SlotReport createSlotReport() {
List<SlotStatus> slotStatuses = new ArrayList<>();
for (Slot slot : slots.values()) {
SlotStatus slotStatus = new SlotStatus(
slot.getSlotId(),
slot.getResourceProfile(),
slot.getJobId(),
slot.getAllocationId());
slotStatuses.add(slotStatus);
}
return new SlotReport(slotStatuses);
}
public void addSlot(SlotID slotId, JobID jobId, AllocationID allocationId) {
Slot slot = new Slot(slotId, taskManagerLocation, jobId, allocationId);
slots.put(slotId, slot);
}
}
SlotTable 维护本地 Slot 的映射,createSlotReport() 遍历所有 Slot 创建 SlotStatus 列表,封装为 SlotReport 返回。每个 SlotStatus 包含四个关键字段。
3.3 SlotReport 与 SlotStatus
java
public class SlotReport implements Serializable {
private final Iterable<SlotStatus> slotStatuses;
public SlotReport(Iterable<SlotStatus> slotStatuses) {
this.slotStatuses = slotStatuses;
}
public Iterable<SlotStatus> iterator() {
return slotStatuses;
}
}
public class SlotStatus implements Serializable {
private final SlotID slotID;
private final ResourceProfile resourceProfile;
private final JobID jobID;
private final AllocationID allocationID;
public SlotStatus(
SlotID slotID,
ResourceProfile resourceProfile,
JobID jobID,
AllocationID allocationID) {
this.slotID = slotID;
this.resourceProfile = resourceProfile;
this.jobID = jobID;
this.allocationID = allocationID;
}
public SlotID getSlotID() { return slotID; }
public ResourceProfile getResourceProfile() { return resourceProfile; }
public JobID getJobID() { return jobID; }
public AllocationID getAllocationID() { return allocationID; }
}
SlotReport 是 SlotStatus 的可迭代集合,SlotStatus 是不可变的数据类,包含 slotID、resourceProfile、jobID、allocationID 四个字段。FREE 状态的 Slot,jobID 和 allocationID 为 null。
3.4 ResourceManagerImpl 转发
java
public class ResourceManagerImpl<WorkerType extends ResourceIDRetrievable>
extends FencedRpcEndpoint<ResourceManagerId>
implements ResourceManager<WorkerType> {
private final SlotManager slotManager;
private final Map<ResourceID, TaskExecutorConnection> taskManagerConnections;
@Override
public CompletableFuture<RegistrationResponse> registerTaskManager(
String taskManagerRpcAddress,
TaskManagerLocation taskManagerLocation,
TaskManagerConfiguration taskManagerConfiguration,
SlotReport slotReport,
ResourceID resourceID) {
// 验证 fencing token
validateRunsInMainThread();
// 创建 TaskExecutorConnection
TaskExecutorConnection taskExecutorConnection =
new TaskExecutorConnection(taskManagerLocation, taskManagerGateway);
// 注册到 SlotManager
slotManager.registerTaskManager(
taskExecutorConnection,
slotReport);
// 保存连接
taskManagerConnections.put(resourceID, taskExecutorConnection);
return CompletableFuture.completedFuture(
new TaskManagerRegistrationSuccess(resourceManagerId));
}
}
ResourceManagerImpl 收到注册请求后,创建 TaskExecutorConnection,调用 slotManager.registerTaskManager() 转发 SlotReport,保存连接用于后续通信。
3.5 SlotManagerImpl 注册 Slot
java
public class SlotManagerImpl implements SlotManager {
private final Map<ResourceID, Map<SlotID, TaskManagerSlot>> taskManagerSlots;
private final SlotStatusSyncer slotStatusSyncer;
@Override
public boolean registerTaskManager(
TaskExecutorConnection taskExecutorConnection,
SlotReport initialSlotReport) {
ResourceID taskManagerId = taskExecutorConnection.getResourceID();
// 检查是否已注册
if (taskManagerSlots.containsKey(taskManagerId)) {
return false;
}
// 创建 TaskManager 的 Slot 映射
Map<SlotID, TaskManagerSlot> slots = new HashMap<>();
// 遍历 SlotReport,为每个 Slot 创建 TaskManagerSlot
for (SlotStatus slotStatus : initialSlotReport) {
TaskManagerSlot taskManagerSlot = new TaskManagerSlot(
slotStatus.getSlotID(),
slotStatus.getResourceProfile(),
taskManagerId,
taskExecutorConnection);
// 注册状态监听器
taskManagerSlot.setSlotStatusListener(slotStatusSyncer);
slots.put(slotStatus.getSlotID(), taskManagerSlot);
}
taskManagerSlots.put(taskManagerId, slots);
return true;
}
}
SlotManagerImpl 是注册的核心执行者。遍历 SlotReport 中的每个 SlotStatus,创建 TaskManagerSlot 实例,注册 SlotStatusListener,加入 taskManagerSlots 映射管理。
3.6 TaskManagerSlot 状态管理
java
public class TaskManagerSlot {
private final SlotID slotId;
private final ResourceProfile resourceProfile;
private final ResourceID taskManagerId;
private final TaskExecutorConnection taskManagerConnection;
private SlotState state;
private JobID jobId;
private AllocationID allocationId;
private SlotStatusListener slotStatusListener;
public TaskManagerSlot(
SlotID slotId,
ResourceProfile resourceProfile,
ResourceID taskManagerId,
TaskExecutorConnection taskManagerConnection) {
this.slotId = slotId;
this.resourceProfile = resourceProfile;
this.taskManagerId = taskManagerId;
this.taskManagerConnection = taskManagerConnection;
this.state = SlotState.FREE;
}
// FREE → PENDING
public void assignPendingAllocation(AllocationID allocationId, JobID jobId) {
Preconditions.checkState(state == SlotState.FREE,
"Slot is not in FREE state, current state: " + state);
this.state = SlotState.PENDING;
this.allocationId = allocationId;
this.jobId = jobId;
}
// PENDING → ALLOCATED
public void markAllocated() {
Preconditions.checkState(state == SlotState.PENDING,
"Slot is not in PENDING state, current state: " + state);
this.state = SlotState.ALLOCATED;
if (slotStatusListener != null) {
slotStatusListener.notifySlotStatusChange(slotId, state);
}
}
// ALLOCATED → FREE
public void freeSlot() {
Preconditions.checkState(state == SlotState.ALLOCATED,
"Slot is not in ALLOCATED state, current state: " + state);
this.state = SlotState.FREE;
this.allocationId = null;
this.jobId = null;
}
}
TaskManagerSlot 是 Slot 的运行时表示,维护 Slot 状态。每个状态转换方法都有严格的前置条件检查(Preconditions.checkState),防止非法状态转换。状态变化时通知 SlotStatusListener。
四、Slot 状态机与心跳更新
下面这张图是 Slot 状态机与心跳更新,包括四种状态、状态转换触发条件、心跳更新流程和配置最佳实践。

Slot 四种状态
Slot 有四种状态,构成完整的生命周期:
- FREE:空闲状态,可分配给作业使用。初始注册时所有 Slot 都是 FREE 状态。
- PENDING:分配中状态,SlotManager 已选择该 Slot,但还在等待 TaskExecutor 确认分配。
- ALLOCATED:已分配状态,TaskExecutor 已确认分配,Slot 绑定到特定作业,可部署 Task 执行。
- RELEASED:已释放状态,Slot 正在释放中,等待 TaskExecutor 确认释放后回到 FREE。
状态转换触发条件
状态转换有6种,每种都有明确的触发方法:
- FREE → PENDING:assignPendingAllocation(),SlotManager 选择 Slot 分配时触发。
- PENDING → ALLOCATED:markAllocated(),TaskExecutor 确认分配成功时触发。
- ALLOCATED → FREE:freeSlot(),作业完成或取消,Slot 释放时触发。
- PENDING → FREE:cancelPendingAllocation(),分配超时或取消时触发。
- ALLOCATED → RELEASED:releaseSlot(),TaskManager 关闭或 Slot 主动释放时触发。
- RELEASED → FREE:resetSlot(),TaskExecutor 确认释放完成时触发。
心跳更新 Slot 状态
心跳机制是 Slot 状态同步的关键:
- 定时触发:HeartbeatManager 每 10 秒(heartbeat.interval)触发一次心跳。
- 创建 SlotReport:TaskExecutor 从 SlotTable 获取最新 Slot 状态,创建 SlotReport。
- 发送心跳:TaskExecutor 向 ResourceManager 发送心跳,携带 SlotReport。
- RM 转发:ResourceManager 调用 slotManager.reportSlotStatus() 转发 SlotReport。
- 状态更新:SlotManager 对比新旧 SlotReport,发现状态变化时更新 TaskManagerSlot 状态,触发 PendingSlotRequest 分配。
超过 50 秒(heartbeat.timeout)无心跳的 TaskManager 会被 ResourceManager 剔除,其所有 Slot 被标记为不可用。
五、关键配置参数
yaml
# 每个 TaskManager 的 Slot 数
taskmanager.numberOfTaskSlots: 1
# 心跳间隔
heartbeat.interval: 10s
# 心跳超时
heartbeat.timeout: 50s
# TaskManager 超时(SlotManager 剔除 TM)
slotmanager.taskmanager-timeout: 30s
# Slot 请求超时
slotmanager.request-timeout: 5min
# TaskManager 注册超时
taskmanager.registration.timeout: 5min
# TaskManager 总内存
taskmanager.memory.process.size: 1728m
# 网络内存比例
taskmanager.memory.network.fraction: 0.1
关键配置说明:
- numberOfTaskSlots:每个 TaskManager 的 Slot 数量,决定可同时执行的 Task 数。根据 CPU 核数配置,通常 1-4 个。
- heartbeat.interval:心跳间隔,默认 10 秒。间隔越短,状态同步越及时,但网络开销越大。
- heartbeat.timeout:心跳超时,默认 50 秒。超过该时间无心跳则 TM 被剔除。网络不稳定时可调大。
- slotmanager.taskmanager-timeout:SlotManager 剔除 TM 的超时,默认 30 秒。注意与 heartbeat.timeout 的配合。
- slotmanager.request-timeout:Slot 请求超时,默认 5 分钟。超过该时间未分配则请求失败。
- taskmanager.registration.timeout:TM 注册超时,默认 5 分钟。注册失败会持续重试直到超时。
六、常见问题与最佳实践
常见问题
-
Slot 注册失败
- 原因:ResourceManager 地址错误或网络不通。
- 解决:检查 jobmanager.rpc.address 配置,确认网络连通,查看 RM 日志中的注册请求记录。
-
Slot 状态不一致
- 原因:心跳丢失或 SlotStatusSyncer 异常,导致 SlotManager 中的状态与 TaskManager 实际状态不一致。
- 解决:检查心跳间隔和超时配置,重启 JobManager 恢复状态,查看 SlotStatusSyncer 日志。
-
Slot 数量不匹配
- 原因:TaskManager 的 numberOfTaskSlots 配置与实际注册的 Slot 数不一致。
- 解决:检查 TaskManager 和 ResourceManager 的配置,确保 Slot 数一致,查看注册时的 SlotReport。
-
Slot 注册后立即被剔除
- 原因:心跳超时或 TaskManager 负载过高导致心跳响应慢。
- 解决:调大 heartbeat.timeout 和 slotmanager.taskmanager-timeout,检查 TaskManager 负载,增加资源。
-
Slot 泄漏
- 原因:作业异常结束时 Slot 未正确释放,导致 Slot 一直处于 ALLOCATED 状态。
- 解决:监控 ALLOCATED 状态的 Slot 数量,检查作业取消逻辑,确保 Slot 正确释放。
最佳实践
-
合理配置 Slot 数:根据 CPU 核数配置 numberOfTaskSlots,通常 1-4 个。Slot 数过多会导致每个 Slot 分配的内存和 CPU 减少,影响 Task 执行性能;过少则资源利用率低。
-
监控 Slot 状态:关注 FREE/ALLOCATED/PENDING Slot 数量变化。FREE Slot 持续为 0 说明资源不足,ALLOCATED Slot 持续不释放可能存在 Slot 泄漏,PENDING Slot 堆积说明分配有问题。
-
配置合适心跳:心跳间隔 10 秒,超时 50 秒是默认推荐配置。网络不稳定的环境可调大超时时间到 80-100 秒,避免频繁剔除和重新注册。
-
使用 Slot Sharing:默认开启 Slot Sharing,多个算子共享一个 Slot,提高资源利用率。但隔离性要求高的场景(如内存密集型算子、需要独立 JVM 的场景)可以关闭 Slot Sharing,为每个算子分配独立 Slot。
-
配置 HA 高可用:生产环境配置 ZooKeeper/K8s HA,ResourceManager 故障切换时 TaskManager 自动重新注册,不需要重启 TaskManager,提高集群可用性。同时配置 Zookeeper 会话超时,避免网络抖动导致频繁切换。
-
合理配置超时:slotmanager.request-timeout 根据作业规模配置,大作业可调大到 10-15 分钟,避免资源申请频繁超时。taskmanager.registration.timeout 保持默认 5 分钟即可,注册失败会自动重试。
七、总结
Flink 注册 Slot 是一个6步流程:TaskManager 注册 → 创建 SlotReport → RM 转发 → SlotManager 创建 TaskManagerSlot → 注册 SlotStatusListener → 心跳更新状态。核心类包括 TaskExecutor(创建 SlotReport 和注册)、SlotTable(管理本地 Slot)、SlotReport/SlotStatus(数据结构)、ResourceManagerImpl(转发)、SlotManagerImpl(创建和管理 TaskManagerSlot)、TaskManagerSlot(Slot 状态管理)。
Slot 有四种状态:FREE(空闲可分配)、PENDING(分配中等待确认)、ALLOCATED(已分配执行 Task)、RELEASED(已释放待回收)。状态转换有严格的前置条件检查,通过 assignPendingAllocation()、markAllocated()、freeSlot() 等方法触发。心跳机制每 10 秒上报一次 SlotReport,超过 50 秒无心跳则被剔除。
理解注册 Slot 的源码实现和状态机,有助于排查 Slot 注册失败、状态不一致、Slot 泄漏、心跳超时等常见问题,为 Flink 集群的稳定运行提供保障。