Flink Slot 分配机制深度解析:从申请到部署的完整链路

前言

当你提交一个 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);

流程回顾

  1. 阶段一 :JobMaster 生成 AllocationID,创建 PendingRequest
  2. 阶段二 :将 AllocationID 包含在资源需求中
  3. 阶段三 :ResourceManager 将 AllocationID 传递给 TaskExecutor
  4. 阶段四 :TaskExecutor 提供 Slot 时携带 AllocationID
  5. 阶段五 :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() - 接收 Slot
  • DefaultScheduler.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);
    }
}

恢复流程:

  1. 释放失效 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"));
    }
}
  1. 触发 Region 故障恢复
java 复制代码
// DefaultScheduler.java
@Override
public void handleGlobalFailure(Throwable cause) {
    
    // 根据 Failover 策略决定重启范围
    Set<ExecutionVertexID> verticesToRestart = 
        failoverStrategy.getTasksNeedingRestart(failedVertexId, cause);
    
    // 回滚到最近的 Checkpoint
    restoreFromCheckpoint(verticesToRestart);
    
    // 重新调度
    allocateSlotsAndDeploy(verticesToRestart);
}
  1. 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 状态?

可能原因

  1. Slot 不足
yaml 复制代码
# 检查 JobManager 日志
No available slot for ExecutionVertex

# 解决方案:增加 TaskManager 数量或 Slot 数
taskmanager.numberOfTaskSlots: 4
  1. 资源规格不匹配
arduino 复制代码
// 检查配置
taskmanager.memory.process.size: 2g
# vs
env.setParallelism(100)  // 并行度过高
  1. 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 分配问题?

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 关键设计思想

  1. 异步非阻塞:所有 RPC 调用都是异步的,通过 CompletableFuture 协调
  2. 精确匹配:AllocationID 贯穿整个流程,确保 Slot 分配的准确性
  3. 资源复用:SharedSlot 通过树形结构实现多 Task 共享
  4. 故障恢复:心跳机制 + Region 故障恢复 + Checkpoint,保证高可用
  5. 动态扩展:YARN 模式支持根据需求动态申请容器
相关推荐
我变秃了也没变强1 天前
standalone模式部署flink集群
flink·apache
渣渣盟1 天前
Flink 流式写入文件终极指南:从 Row Format 到 Bucket 分区的深度剖析
大数据·flink
CIO_Alliance1 天前
AI算法系列(3)| 实时数据管道:CDC+Flink实现库存异动秒级响应
大数据·人工智能·flink
渣渣盟2 天前
Flink 写入 Redis 实战:数据模型选型、连接池调优与常见坑
大数据·redis·flink
渣渣盟3 天前
Flink + Kafka 数据写入实战:从API调用到端到端一致性精讲
flink·kafka·linq
TDengine (老段)7 天前
TDengine 第三方工具 — Telegraf、Kafka Connect、Flink、Spark
大数据·数据库·物联网·flink·kafka·时序数据库·tdengine
头茬韭菜8 天前
第 4 篇:「Fluss + Flink 集成实战」—— Catalog、Source 与 Sink
大数据·python·flink·fluss
阿里云大数据AI技术10 天前
官宣|Apache Fluss 毕业成为顶级项目,湖流一体开启 Agentic Lake 全面实时化时代
人工智能·flink
Blossom i10 天前
分布式编程实验二:Flink安装与编程实践(头歌云客)
大数据·分布式·flink