前言
当你提交一个 Flink 作业后,JobMaster 如何向 ResourceManager 申请 Slot?ResourceManager 又如何从 TaskExecutor 获取资源?TaskExecutor 提供 Slot 后,JobMaster 如何接收并部署 Task?
本文将基于 Flink 1.17 源码 ,完整剖析 Slot 从申请到部署的 5 大阶段。
一、整体流程概览
发起 Slot 请求
声明资源需求
申请容器(YARN 模式)分配容器启动 TaskExecutor
请求分配 Slot
提供 Slot部署
核心角色职责
| 组件 | 职责 | 关键类 |
|---|---|---|
| JobMaster | 作业调度协调者 | DefaultScheduler |
| SlotPool | JobMaster 的 Slot 资源池 | DeclarativeSlotPoolBridge |
| ResourceManager | 集群资源管理者 | YarnResourceManager / StandaloneResourceManager |
| SlotManager | ResourceManager 的 Slot 分配器 | DeclarativeSlotManager |
| TaskExecutor | 任务执行节点 | TaskExecutor |
二、阶段一:JobMaster 发起 Slot 请求
2.1 触发时机
当 ExecutionGraph 构建完成后,Scheduler 开始调度任务:
java
// DefaultScheduler.java
@Override
public void startScheduling() {
// 转换状态:CREATED -> SCHEDULED
transitionToScheduled(getExecutionGraph().getAllExecutionVertices());
// 分配 Slot 并部署任务
allocateSlotsAndDeploy(executionVertexDeploymentOptions);
}
2.2 核心流程:allocateSlotsAndDeploy()
java
private void allocateSlotsAndDeploy(
List<ExecutionVertexDeploymentOption> executionVertexDeploymentOptions) {
// 1. 转换 ExecutionVertex 状态为 SCHEDULED
transitionToScheduled(executionVertices);
// 2. 创建 SlotProfile 检索器(包含位置偏好、资源需求等)
Map<ExecutionVertexID, SlotProfile> slotProfiles =
createSlotProfiles(executionVertexDeploymentOptions);
// 3. 按 SlotSharingGroup 分组
Map<SlotSharingGroup, List<ExecutionVertexID>> groupedVertices =
groupBySlotSharingGroup(executionVertices);
// 4. 为每个 SlotSharingGroup 分配一个 SharedSlot
for (Map.Entry<SlotSharingGroup, List<ExecutionVertexID>> entry :
groupedVertices.entrySet()) {
SlotSharingGroup group = entry.getKey();
List<ExecutionVertexID> vertices = entry.getValue();
// 5. 生成 Slot 请求 ID(AllocationID)
AllocationID allocationId = new AllocationID();
// 6. 计算 Slot 需要的资源
ResourceProfile resourceProfile =
calculateResourceProfile(group, vertices);
// 7. 获取 Slot 的偏好位置(TaskManager)
Collection<TaskManagerLocation> preferredLocations =
getPreferredLocations(vertices);
// 8. 创建 Slot 请求对象
SlotRequestId slotRequestId = new SlotRequestId();
PhysicalSlotRequest slotRequest = new PhysicalSlotRequest(
slotRequestId,
allocationId,
resourceProfile,
preferredLocations
);
// 9. 尝试从可用 Slot 分配
Optional<PhysicalSlot> availableSlot =
slotPool.tryAllocateFromAvailable(slotRequest);
if (availableSlot.isPresent()) {
// 直接使用可用 Slot
deployTask(vertices, availableSlot.get());
} else {
// 10. 请求新的 Slot
requestNewSlot(slotRequest);
}
}
}
2.3 关键概念:SharedSlot
什么是 SharedSlot?
SharedSlot 允许同一 SlotSharingGroup 的多个 SubTask 复用同一个物理 Slot。
内部结构 :SharedSlot 内部是一个 MultiTaskSlot 树形结构:
scss
SharedSlot (物理 Slot)
└── MultiTaskSlot (Root)
├── MultiTaskSlot (Source, parallelism=4)
│ ├── SingleTaskSlot (Source-0)
│ ├── SingleTaskSlot (Source-1)
│ ├── SingleTaskSlot (Source-2)
│ └── SingleTaskSlot (Source-3)
├── MultiTaskSlot (Map, parallelism=4)
│ ├── SingleTaskSlot (Map-0)
│ └── ...
└── MultiTaskSlot (Sink, parallelism=4)
└── ...
生命周期管理:
就像 GC 的引用计数,每个 MultiTaskSlot 都被对应的子任务"引用"。SharedSlot 的引用计数等于树上所有活跃子任务的数量。只有当所有子任务都结束(正常或异常),计数归零,物理 Slot 才会被释放。
示例:
java
// 假设有 3 个算子,并行度都是 4
Source(4) -> Map(4) -> Sink(4)
// 不使用 Slot Sharing:需要 4 + 4 + 4 = 12 个 Slot
// 使用 Slot Sharing:只需要 max(4, 4, 4) = 4 个 Slot
// 每个 SharedSlot 内部运行:
// Slot-0: Source-0, Map-0, Sink-0
// Slot-1: Source-1, Map-1, Sink-1
// Slot-2: Source-2, Map-2, Sink-2
// Slot-3: Source-3, Map-3, Sink-3
2.4 关键源码位置
DefaultScheduler.allocateSlotsAndDeploy()- 分配入口SlotSharingExecutionSlotAllocator.allocateSlotsFor()- 分配逻辑DeclarativeSlotPoolBridge.allocateSlot()- 池化管理
三、阶段二:声明资源需求
3.1 requestNewSlot() 方法
当 SlotPool 中没有可用 Slot 时,会调用此方法:
java
private void requestNewSlot(PhysicalSlotRequest slotRequest) {
// 1. 判断作业类型
boolean isStreamingJob = executionMode == ExecutionMode.STREAMING;
if (isStreamingJob) {
// 流式作业:请求长期占用的 Slot
requestBulkSlot(slotRequest);
} else {
// 批处理作业:请求临时 Slot
requestSingleSlot(slotRequest);
}
}
private void requestBulkSlot(PhysicalSlotRequest slotRequest) {
// 2. 创建待处理的请求对象
PendingRequest pendingRequest = new PendingRequest(
slotRequest.getSlotRequestId(),
slotRequest.getAllocationId(),
slotRequest.getResourceProfile()
);
// 3. 放入待处理队列
pendingRequests.put(
slotRequest.getAllocationId(),
pendingRequest
);
// 4. 累加资源需求
totalResourceRequirements.add(slotRequest.getResourceProfile());
// 5. 通过回调函数通知 ResourceManager
resourceRequirementChangeListener.notifyResourceRequirementsChanged(
jobId,
totalResourceRequirements
);
}
3.2 PendingRequest 的作用
PendingRequest 是 Slot 请求和 Slot 分配之间的桥梁:
java
class PendingRequest {
private final SlotRequestId slotRequestId; // 请求标识
private final AllocationID allocationId; // 分配标识
private final ResourceProfile resourceProfile; // 资源需求
private final CompletableFuture<PhysicalSlot> slotFuture; // 异步结果
// 当 Slot 分配完成后,通过这个 Future 通知调度器
}
关键映射关系:
java
// SlotPool 内部维护
Map<AllocationID, PendingRequest> pendingRequests;
// 后续 TaskExecutor 提供 Slot 时,根据 AllocationID 精确匹配
3.3 关键源码位置
DeclarativeSlotPoolBridge.increaseResourceRequirements()- 声明需求SlotPoolImpl.requestNewAllocatedSlot()- 旧版实现ResourceRequirement- 资源需求数据结构
四、阶段三:ResourceManager 分配 Slot
4.1 接收资源需求
ResourceManager 收到 JobMaster 的资源需求后:
java
// YarnResourceManager.java
@Override
public void notifySlotRequirements(
JobID jobId,
Collection<ResourceRequirement> resourceRequirements) {
// 1. 调用 SlotManager 处理资源需求
slotManager.processResourceRequirements(resourceRequirements);
}
4.2 SlotManager 的分配逻辑
java
// DeclarativeSlotManager.java
private void processResourceRequirements(
Collection<ResourceRequirement> requirements) {
// 1. 通知 ResourceTracker 更新资源需求
resourceTracker.notifyResourceRequirements(jobId, requirements);
// 2. 为每个 Job 分配 Slot(逻辑分配)
for (ResourceRequirement requirement : requirements) {
// 3. 检查是否有空闲的 TaskExecutor
Optional<TaskExecutorConnection> availableTM =
findAvailableTaskExecutor(requirement.getResourceProfile());
if (availableTM.isPresent()) {
// 4. 向 TaskExecutor 发送 Slot 请求
requestSlotFromTaskExecutor(
availableTM.get(),
requirement
);
} else {
// 5. 没有可用 TaskExecutor,需要启动新的
startNewTaskExecutor(requirement.getResourceProfile());
}
}
}
4.3 YARN 模式的特殊处理:动态申请容器
在 YARN 模式下,ResourceManager 会动态向 YARN 申请容器:
java
// YarnResourceManager.java
private void startNewTaskExecutor(ResourceProfile resourceProfile) {
// 1. 将 Slot 请求转换为 YARN 的 ContainerRequest
Resource resource = Resource.newInstance(
(int) resourceProfile.getTotalMemory().getMebiBytes(),
resourceProfile.getCpuCores().getValue().intValue()
);
Priority priority = Priority.newInstance(1);
ContainerRequest containerRequest = new ContainerRequest(
resource,
null, // 节点偏好
null, // 机架偏好
priority
);
// 2. 发起异步容器申请
yarnClient.addContainerRequest(containerRequest);
}
// YARN 回调方法
@Override
public void onContainersAllocated(List<Container> containers) {
for (Container container : containers) {
// 3. 在对应 NodeManager 上启动 TaskExecutor
launchTaskExecutor(container);
}
}
private void launchTaskExecutor(Container container) {
// 4. 构造启动命令
String command = String.format(
"$JAVA_HOME/bin/java -Xmx%dm %s %s",
container.getResource().getMemory(),
TaskManagerRunner.class.getName(),
configurationDirectory
);
// 5. 提交到 NodeManager
nodeManagerClient.startContainer(container, command);
}
流程总结:
scss
JobMaster 需要 Slot
↓
ResourceManager 收到需求
↓
检查是否有空闲 TaskExecutor
↓ (没有)
向 YARN 申请容器 (ContainerRequest)
↓
YARN ResourceManager 分配容器
↓
在 NodeManager 上启动 TaskExecutor
↓
TaskExecutor 启动后注册到 ResourceManager
↓
ResourceManager 请求 TaskExecutor 分配 Slot
4.4 关键源码位置
DeclarativeSlotManager.processResourceRequirements()- 需求处理YarnResourceManager.onContainersAllocated()- YARN 回调TaskExecutorProcessSpec- TaskExecutor 资源规格
五、阶段四:TaskExecutor 提供 Slot
5.1 TaskExecutor 接收请求
ResourceManager 向 TaskExecutor 发送 Slot 请求后:
java
// TaskExecutor.java
@Override
public CompletableFuture<Acknowledge> requestSlot(
SlotID slotId,
JobID jobId,
AllocationID allocationId,
ResourceProfile resourceProfile,
String targetAddress,
ResourceManagerId resourceManagerId,
Time timeout) {
// 1. 验证是否已连接到 ResourceManager
if (!isConnectedToResourceManager(resourceManagerId)) {
return FutureUtils.completedExceptionally(
new TaskManagerException("Not connected to ResourceManager")
);
}
// 2. 持久化 Slot 分配快照(用于故障恢复)
taskSlotTable.allocateSlot(
slotId,
jobId,
allocationId,
resourceProfile,
timeout
);
// 3. 调用核心分配方法
return allocateSlotForJob(jobId, slotId, allocationId, resourceProfile);
}
5.2 allocateSlotForJob() 核心逻辑
java
private CompletableFuture<Acknowledge> allocateSlotForJob(
JobID jobId,
SlotID slotId,
AllocationID allocationId,
ResourceProfile resourceProfile) {
// 1. 获取或创建 Job 对象
JobTable.Job job = jobTable.getOrCreateJob(jobId);
// 2. 注册到 JobMaster(建立连接)
CompletableFuture<JobMasterGateway> jobMasterGatewayFuture =
jobMasterGatewayFuture.getOrConnect(jobId);
return jobMasterGatewayFuture.thenCompose(jobMasterGateway -> {
// 3. 向 JobMaster 提供 Slot
return jobMasterGateway.offerSlots(
getResourceID(), // TaskExecutor ID
Collections.singleton(
new SlotOffer(
allocationId,
slotId.getSlotNumber(),
resourceProfile
)
),
timeout
);
});
}
5.3 TaskSlotTable 的作用
TaskSlotTable 是 TaskExecutor 内部的 Slot 状态管理器:
java
class TaskSlotTable {
// Slot 状态枚举
enum State {
FREE, // 空闲
ALLOCATED, // 已分配(但未部署 Task)
ACTIVE // 活跃(正在运行 Task)
}
// 内部数据结构
Map<SlotID, TaskSlot> slots; // Slot ID -> Slot 对象
Map<AllocationID, SlotID> allocations; // 分配 ID -> Slot ID
// 分配 Slot
boolean allocateSlot(SlotID slotId, JobID jobId, AllocationID allocationId) {
TaskSlot slot = slots.get(slotId);
if (slot.getState() != State.FREE) {
return false; // Slot 不可用
}
slot.setState(State.ALLOCATED);
slot.setJobId(jobId);
slot.setAllocationId(allocationId);
allocations.put(allocationId, slotId);
return true;
}
}
5.4 关键源码位置
TaskExecutor.requestSlot()- 请求入口TaskSlotTable.allocateSlot()- 状态管理TaskExecutor.offerSlotsToJobManager()- 提供 Slot
六、阶段五:JobMaster 接收 Slot
6.1 接收 TaskExecutor 的 Slot Offer
JobMaster 收到 TaskExecutor 的 Slot 提供后:
java
// JobMaster.java
@Override
public CompletableFuture<Collection<SlotOffer>> offerSlots(
ResourceID taskManagerId,
Collection<SlotOffer> slots,
Time timeout) {
// 委托给 SlotPool 处理
return slotPoolService.offerSlots(taskManagerId, slots, timeout);
}
// DeclarativeSlotPoolBridge.java
@Override
public CompletableFuture<Collection<SlotOffer>> offerSlots(
ResourceID taskManagerId,
Collection<SlotOffer> slots,
Time timeout) {
List<SlotOffer> acceptedSlots = new ArrayList<>();
for (SlotOffer slotOffer : slots) {
AllocationID allocationId = slotOffer.getAllocationId();
// 1. 根据 AllocationID 查找之前创建的 PendingRequest
PendingRequest pendingRequest = pendingRequests.get(allocationId);
if (pendingRequest == null) {
// 没有对应的请求,拒绝此 Slot
continue;
}
// 2. 检查资源是否满足需求
ResourceProfile required = pendingRequest.getResourceProfile();
ResourceProfile offered = slotOffer.getResourceProfile();
if (!offered.isMatching(required)) {
// 资源不匹配,拒绝
continue;
}
// 3. 创建 AllocatedSlot 对象
AllocatedSlot allocatedSlot = new AllocatedSlot(
allocationId,
taskManagerId,
slotOffer.getSlotIndex(),
offered,
taskManagerGateway
);
// 4. 将 Slot 加入可用资源池
availableSlots.put(allocationId, allocatedSlot);
// 5. 完成 PendingRequest 的 Future
pendingRequest.getFuture().complete(allocatedSlot);
// 6. 从待处理队列移除
pendingRequests.remove(allocationId);
acceptedSlots.add(slotOffer);
}
return CompletableFuture.completedFuture(acceptedSlots);
}
6.2 精确匹配机制
Q1:如何将物理 Slot 与 PendingRequest 精确匹配?
通过 AllocationID 作为唯一标识:
java
// SlotPool 内部维护映射
Map<AllocationID, PendingRequest> pendingRequests;
// 匹配逻辑
PendingRequest pendingRequest = pendingRequests.get(allocationId);
流程回顾:
- 阶段一 :JobMaster 生成
AllocationID,创建PendingRequest - 阶段二 :将
AllocationID包含在资源需求中 - 阶段三 :ResourceManager 将
AllocationID传递给 TaskExecutor - 阶段四 :TaskExecutor 提供 Slot 时携带
AllocationID - 阶段五 :JobMaster 根据
AllocationID匹配PendingRequest
Q2:如果 Slot 资源大于需求,会拒绝吗?
不会拒绝,也不会截断 。Flink 采用最低资源保障策略:
java
// ResourceProfile.java
public boolean isMatching(ResourceProfile required) {
return this.cpuCores.compareTo(required.cpuCores) >= 0
&& this.taskHeapMemory.compareTo(required.taskHeapMemory) >= 0
&& this.taskOffHeapMemory.compareTo(required.taskOffHeapMemory) >= 0
&& this.managedMemory.compareTo(required.managedMemory) >= 0;
}
只要物理 Slot 的资源 ≥ 请求的资源,匹配就成功。多余的资源不会被截断,而是直接分配给这个 Task 使用。
示例:
arduino
请求:2 CPU, 4GB 内存
提供:4 CPU, 8GB 内存
结果:接受,Task 可以使用全部 4 CPU 和 8GB 内存
6.3 部署 Task
Slot 匹配成功后,立即触发 Task 部署:
java
// DefaultScheduler.java
private void deployTask(ExecutionVertex vertex, AllocatedSlot slot) {
// 1. 创建 TaskDeploymentDescriptor
TaskDeploymentDescriptor tdd =
TaskDeploymentDescriptorFactory.fromExecutionVertex(
vertex,
slot.getSlotNumber()
);
// 2. 获取 TaskManager 的 Gateway
TaskExecutorGateway taskExecutorGateway = slot.getTaskManagerGateway();
// 3. RPC 调用部署 Task
CompletableFuture<Acknowledge> deploymentFuture =
taskExecutorGateway.submitTask(tdd, jobMasterGateway, timeout);
// 4. 部署成功后,转换状态:SCHEDULED -> DEPLOYING -> RUNNING
deploymentFuture.thenAccept(ack -> {
vertex.transitionState(ExecutionState.DEPLOYING);
});
}
Q3:部署是在 RPC 主线程还是异步线程?
在 JobMaster 的调度主线程(不是 RPC 主线程):
java
// JobMaster 内部的 MainThreadExecutor
private final ComponentMainThreadExecutor mainThreadExecutor;
// 所有调度操作都在这个线程执行
mainThreadExecutor.execute(() -> {
// 状态转换
vertex.transitionState(ExecutionState.DEPLOYING);
// ExecutionGraph 更新
executionGraph.notifyVertexStateChange(vertex);
});
这个线程专门负责:
- 状态转换(SCHEDULED → DEPLOYING → RUNNING)
- ExecutionGraph 更新
- Checkpoint 协调
- Failover 处理
6.4 关键源码位置
DeclarativeSlotPoolBridge.offerSlots()- 接收 SlotDefaultScheduler.deployOrHandleError()- 部署任务Execution.deploy()- 部署核心逻辑
七、故障场景与恢复机制
7.1 TaskManager 宕机怎么办?
问题:如果 SharedSlot 所属的 TaskManager 突然宕机,JobMaster 如何感知?后续如何处理?
感知机制:心跳超时
java
// HeartbeatManager.java
private void checkHeartbeatTimeout(ResourceID taskManagerId) {
long lastHeartbeat = lastHeartbeatTimestamps.get(taskManagerId);
long now = System.currentTimeMillis();
if (now - lastHeartbeat > heartbeatTimeout) {
// 心跳超时,认为 TaskManager 已失联
listener.notifyHeartbeatTimeout(taskManagerId);
}
}
恢复流程:
- 释放失效 Slot
java
// SlotPool.java
@Override
public void notifyTaskManagerFailure(ResourceID taskManagerId) {
// 释放所有属于该 TaskManager 的 Slot
Collection<AllocatedSlot> slots =
allocatedSlots.getSlotsForTaskManager(taskManagerId);
for (AllocatedSlot slot : slots) {
freeSlot(slot.getAllocationId(),
new TaskManagerException("TaskManager failed"));
}
}
- 触发 Region 故障恢复
java
// DefaultScheduler.java
@Override
public void handleGlobalFailure(Throwable cause) {
// 根据 Failover 策略决定重启范围
Set<ExecutionVertexID> verticesToRestart =
failoverStrategy.getTasksNeedingRestart(failedVertexId, cause);
// 回滚到最近的 Checkpoint
restoreFromCheckpoint(verticesToRestart);
// 重新调度
allocateSlotsAndDeploy(verticesToRestart);
}
- Source 从 Checkpoint 恢复
java
// SourceOperator.java
@Override
public void initializeState(StateInitializationContext context) {
// 从 Checkpoint 恢复 offset
ListState<Long> offsetState = context.getOperatorStateStore()
.getListState(new ListStateDescriptor<>("offset", Long.class));
if (context.isRestored()) {
// 恢复模式:读取保存的 offset
for (Long offset : offsetState.get()) {
reader.seek(offset);
}
}
}
7.2 Region 故障恢复策略
问题:一个 SubTask 失败时,Failover 策略如何决定重启哪些 Task?
Region 的定义
Region 由 数据交换类型 决定:
| 边类型 | 特征 | 持久化 | 示例 |
|---|---|---|---|
| Pipelined | 数据实时流转,上下游同时在线 | ❌ | Forward、Rescale、Rebalance |
| Blocking | 下游等上游全部完成才读取 | ✅ | Batch 模式的 Shuffle |
流处理: 所有边默认为 Pipelined → 整个作业是一个 Region → 失败导致全局重启(从 Checkpoint 恢复)
批处理:Flink 按需插入 Blocking 边 → 形成多个 Region → 支持局部故障恢复
Region 故障恢复示例
scss
// 批处理作业
Source(10) --Blocking--> Map(20) --Pipelined--> Filter(20) --Blocking--> Sink(5)
// Region 划分:
// Region-1: Source(10)
// Region-2: Map(20) -> Filter(20)
// Region-3: Sink(5)
// 如果 Filter-5 失败:
// - Region-2 重启(Map 和 Filter 全部重启)
// - Region-1 不受影响(数据已持久化)
// - Region-3 等待 Region-2 恢复
7.3 关键源码位置
HeartbeatManager.checkHeartbeatTimeout()- 心跳检测RestartPipelinedRegionFailoverStrategy- Region 故障恢复CheckpointCoordinator.restoreLatestCheckpointedState()- Checkpoint 恢复
八、常见问题与最佳实践
8.1 为什么任务一直处于 SCHEDULED 状态?
可能原因:
- Slot 不足
yaml
# 检查 JobManager 日志
No available slot for ExecutionVertex
# 解决方案:增加 TaskManager 数量或 Slot 数
taskmanager.numberOfTaskSlots: 4
- 资源规格不匹配
arduino
// 检查配置
taskmanager.memory.process.size: 2g
# vs
env.setParallelism(100) // 并行度过高
- YARN 资源不足
csharp
# 检查 YARN 日志
AM has exceeded maximum number of container allocations
# 解决方案:增加 YARN 队列容量
yarn.scheduler.capacity.root.default.maximum-allocation-vcores: 32
8.2 如何优化 Slot 利用率?
方法 1:启用 Slot Sharing(默认开启)
java
// 默认所有算子在 "default" 组
env.socketTextStream(...).map(...).filter(...)
// 手动设置分组
stream.map(...).slotSharingGroup("heavy-computation");
方法 2:合理设置并行度
java
// 不要所有算子都用相同并行度
Source(2) // 读取速度慢,低并行度
.map(...).setParallelism(8) // CPU 密集,高并行度
.keyBy(...)
.reduce(...).setParallelism(4) // 聚合,中等并行度
.sink(...).setParallelism(2); // 写入有锁,低并行度
方法 3:禁用不合理的 Sharing
java
// 将 CPU 密集型算子隔离到独立 Slot
stream.map(heavyComputation).slotSharingGroup("cpu-intensive");
8.3 Slot 数量怎么计算?
公式:
scss
不使用 Slot Sharing:
总 Slot 数 = Σ(每个算子的并行度)
使用 Slot Sharing:
总 Slot 数 = max(各算子的并行度)
示例:
scss
Source(4) -> Map(8) -> KeyBy -> Reduce(8) -> Sink(2)
// 不共享:4 + 8 + 8 + 2 = 22 个 Slot
// 共享:max(4, 8, 8, 2) = 8 个 Slot
TaskManager 配置:
makefile
# 每个 TaskManager 提供 4 个 Slot
taskmanager.numberOfTaskSlots: 4
# 需要启动的 TaskManager 数量
# 使用 Slot Sharing:8 / 4 = 2 个 TaskManager
# 不使用:22 / 4 = 6 个 TaskManager
8.4 如何调试 Slot 分配问题?
方法 1:查看 Flink Web UI
rust
JobManager -> Running Jobs -> 点击作业 -> Overview
- 查看 "Total Task Slots" 和 "Available Task Slots"
- 点击 TaskManager 查看每个 Slot 的使用情况
方法 2:启用详细日志
ini
# conf/log4j.properties
logger.slotpool.name = org.apache.flink.runtime.jobmaster.slotpool
logger.slotpool.level = DEBUG
logger.resourcemanager.name = org.apache.flink.runtime.resourcemanager
logger.resourcemanager.level = DEBUG
方法 3:REST API 查询
bash
# 查看 Slot 状态
curl http://localhost:8081/jobs/<job-id>/vertices
# 查看 TaskManager
curl http://localhost:8081/taskmanagers
九、总结
9.1 五阶段核心要点
| 主要角色 | 关键操作 | 核心类 |
|---|---|---|
| JobMaster | 发起 Slot 请求,创建 PendingRequest | DefaultScheduler |
| SlotPool | 声明资源需求,通知 ResourceManager | DeclarativeSlotPoolBridge |
| ResourceManager | 分配 Slot,向 YARN 申请容器 | YarnResourceManager |
| TaskExecutor | 提供 Slot,注册到 JobMaster | TaskExecutor |
| JobMaster | 接收 Slot,部署 Task | Execution.deploy() |
9.2 关键设计思想
- 异步非阻塞:所有 RPC 调用都是异步的,通过 CompletableFuture 协调
- 精确匹配:AllocationID 贯穿整个流程,确保 Slot 分配的准确性
- 资源复用:SharedSlot 通过树形结构实现多 Task 共享
- 故障恢复:心跳机制 + Region 故障恢复 + Checkpoint,保证高可用
- 动态扩展:YARN 模式支持根据需求动态申请容器