Flink SlotPool 申请资源源码跟踪:从调度器请求到Task部署的完整链路

前面讲了 SlotManager 的启动和 Slot 注册流程,这篇聚焦 SlotPool 申请资源的源码实现。SlotPool 运行在 JobMaster 内部,是作业级的 Slot 池,负责向 ResourceManager 申请 Slot、管理已分配的 Slot、提供 Slot 给调度器使用。你有没有想过,SchedulerNG 需要 Slot 时,SlotPool 是如何处理的?无空闲 Slot 时是如何向 RM 申请的?Slot 分配成功后是如何通知调度器的?Slot Sharing 和超时重试是如何实现的?这篇从源码级别跟踪 SlotPool 申请资源的完整链路。


一、SlotPool 整体架构

下面这张图是 SlotPool 整体架构,包括 JobMaster 内部组件、ResourceManager、TaskManager 和申请流程。

SlotPool 运行在 JobMaster 内部,与 SchedulerNG、Execution、JobMasterGateway 协作。SlotPool 内部维护三个核心集合:

  • availableSlots:可用 Slot 集合,存储已经分配但当前空闲的 Slot,可直接分配给新的 Task。
  • allocatedSlots:已分配 Slot 映射,key 为 AllocationID,value 为 AllocatedSlot,记录所有已分配给该作业的 Slot。
  • pendingRequests:等待中请求映射,key 为 AllocationID,value 为 PendingRequest,记录正在向 RM 申请但尚未完成的 Slot 请求。

SlotPool 通过 ResourceManagerGateway 与 ResourceManager 通信,发送 requestSlot RPC 请求。ResourceManager 收到请求后转发给 SlotManager,SlotManager 查找空闲 Slot 并通知 TaskExecutor 分配。分配成功后,ResourceManager 向 SlotPool 发送 offerSlot 通知,SlotPool 接收 Slot 并分配给等待的 Execution。


二、SlotPool 申请资源完整流程

下面这张图是 SlotPool 申请资源完整流程,包括6步申请流程、方法调用链、核心类和常见问题。

SlotPool 申请资源分为6步:

第1步:SchedulerNG 请求 Slot。调度器 SchedulerNG 在部署 Task 时,需要为 Execution 分配 Slot。调用 SlotPool.requestSlot(jobId, allocationId, resourceProfile, targetAddress) 方法,传入作业ID、分配ID、资源需求和目标地址(可选,用于 Slot 亲和性调度)。

第2步:SlotPool 检查可用 Slot。SlotPool 首先检查 availableSlots 集合中是否有满足资源需求的空闲 Slot。如果有,直接从 availableSlots 中取出,加入 allocatedSlots,返回 SlotAndLocality 给调度器。如果没有,则创建 PendingRequest,等待新 Slot 分配。

第3步:向 ResourceManager 申请。无空闲 Slot 时,SlotPool 通过 ResourceManagerGateway 向 ResourceManager 发送 requestSlot RPC 请求。传入 jobId、allocationId、resourceProfile、targetAddress、resourceManagerId(fencing token)和超时时间。RM 收到请求后转发给 SlotManager 处理。

第4步:等待 RM 分配响应。SlotPool 创建 PendingRequest 记录请求信息,等待 ResourceManager 的分配结果。如果 SlotManager 找到空闲 Slot,会通知 TaskExecutor 分配,分配成功后 RM 向 SlotPool 发送 offerSlot 通知。如果资源不足,RM 会通过 Driver 申请新容器,等待新 TaskManager 注册。

第5步:接收 Slot 并分配。SlotPool 收到 offerSlot(allocationId, slotId, taskManagerLocation) 通知后,创建 AllocatedSlot 实例,加入 allocatedSlots 集合。从 pendingRequests 中移除对应的请求,通知等待的 Execution。如果有多个等待请求,按 FIFO 顺序分配 Slot。

第6步:部署 Task 执行。SchedulerNG 获得 SlotAndLocality 后,将 Execution 部署到对应的 TaskManager 上。通过 TaskExecutorGateway 发送 submitTask 请求,TaskExecutor 创建 Task 实例,分配到 Slot 上启动执行。Task 执行完成后,Slot 释放回 availableSlots,可被其他 Task 复用。


三、核心类源码剖析

3.1 SlotPoolImpl 核心实现

java 复制代码
public class SlotPoolImpl implements SlotPool {
    private final Map<AllocationID, AllocatedSlot> allocatedSlots;
    private final Map<AllocationID, PendingRequest> pendingRequests;
    private final Set<AllocatedSlot> availableSlots;
    private ResourceManagerGateway resourceManagerGateway;

    @Override
    public CompletableFuture<SlotAndLocality> requestSlot(
            JobID jobId,
            AllocationID allocationId,
            ResourceProfile resourceProfile,
            String targetAddress,
            Time timeout) {

        // 1. 检查可用 Slot
        AllocatedSlot allocatedSlot = pollAvailableSlot(resourceProfile);
        if (allocatedSlot != null) {
            allocatedSlots.put(allocationId, allocatedSlot);
            return CompletableFuture.completedFuture(
                new SlotAndLocality(allocatedSlot, Locality.UNKNOWN));
        }

        // 2. 创建 PendingRequest
        PendingRequest pendingRequest = new PendingRequest(
            allocationId, jobId, resourceProfile, targetAddress);
        pendingRequests.put(allocationId, pendingRequest);

        // 3. 向 RM 申请 Slot
        if (resourceManagerGateway != null) {
            resourceManagerGateway.requestSlot(
                jobId,
                allocationId,
                resourceProfile,
                targetAddress,
                resourceManagerId,
                timeout);
        }

        // 4. 返回 Future,等待 offerSlot 通知
        return pendingRequest.getFuture();
    }

    @Override
    public void offerSlot(
            AllocationID allocationId,
            SlotID slotId,
            TaskManagerLocation taskManagerLocation) {

        // 1. 查找对应的 PendingRequest
        PendingRequest pendingRequest = pendingRequests.remove(allocationId);
        if (pendingRequest == null) {
            // 无等待请求,加入 availableSlots
            AllocatedSlot allocatedSlot = new AllocatedSlot(
                allocationId, slotId, taskManagerLocation);
            availableSlots.add(allocatedSlot);
            return;
        }

        // 2. 创建 AllocatedSlot
        AllocatedSlot allocatedSlot = new AllocatedSlot(
            allocationId, slotId, taskManagerLocation);
        allocatedSlots.put(allocationId, allocatedSlot);

        // 3. 完成 PendingRequest 的 Future,通知调度器
        pendingRequest.complete(new SlotAndLocality(allocatedSlot, Locality.UNKNOWN));
    }
}

SlotPoolImpl 是 SlotPool 的核心实现。requestSlot 方法首先检查 availableSlots,有空闲则直接分配;无空闲则创建 PendingRequest 并向 RM 发送 requestSlot 请求,返回 Future 等待结果。offerSlot 方法接收 RM 的分配通知,查找对应的 PendingRequest,创建 AllocatedSlot,完成 Future 通知调度器;如果没有等待请求,则加入 availableSlots 供后续使用。

3.2 PendingRequest 等待请求

java 复制代码
public class PendingRequest {
    private final AllocationID allocationId;
    private final JobID jobId;
    private final ResourceProfile resourceProfile;
    private final String targetAddress;
    private final CompletableFuture<SlotAndLocality> future;
    private final long creationTime;

    public PendingRequest(
            AllocationID allocationId,
            JobID jobId,
            ResourceProfile resourceProfile,
            String targetAddress) {
        this.allocationId = allocationId;
        this.jobId = jobId;
        this.resourceProfile = resourceProfile;
        this.targetAddress = targetAddress;
        this.future = new CompletableFuture<>();
        this.creationTime = System.currentTimeMillis();
    }

    public void complete(SlotAndLocality slotAndLocality) {
        future.complete(slotAndLocality);
    }

    public void completeExceptionally(Throwable cause) {
        future.completeExceptionally(cause);
    }

    public boolean isTimeout(long timeout) {
        return System.currentTimeMillis() - creationTime > timeout;
    }
}

PendingRequest 封装了等待中的 Slot 请求,包含 allocationId、jobId、resourceProfile、targetAddress 等请求信息,以及一个 CompletableFuture 用于异步通知分配结果。creationTime 记录请求创建时间,用于超时检测。Slot 分配成功时调用 complete(),分配失败或超时时调用 completeExceptionally()。

3.3 AllocatedSlot 已分配 Slot

java 复制代码
public class AllocatedSlot {
    private final AllocationID allocationId;
    private final SlotID slotId;
    private final TaskManagerLocation taskManagerLocation;
    private final ResourceProfile resourceProfile;

    public AllocatedSlot(
            AllocationID allocationId,
            SlotID slotId,
            TaskManagerLocation taskManagerLocation) {
        this.allocationId = allocationId;
        this.slotId = slotId;
        this.taskManagerLocation = taskManagerLocation;
        this.resourceProfile = ResourceProfile.UNKNOWN;
    }

    public AllocationID getAllocationId() { return allocationId; }
    public SlotID getSlotId() { return slotId; }
    public TaskManagerLocation getTaskManagerLocation() { return taskManagerLocation; }
    public ResourceProfile getResourceProfile() { return resourceProfile; }
}

AllocatedSlot 表示已分配给作业的 Slot,包含 allocationId(分配唯一标识)、slotId(Slot 标识)、taskManagerLocation(TaskManager 位置信息)、resourceProfile(资源描述)。AllocatedSlot 是不可变的,创建后不能修改。

3.4 SlotAndLocality Slot 与位置

java 复制代码
public class SlotAndLocality {
    private final AllocatedSlot slot;
    private final Locality locality;

    public SlotAndLocality(AllocatedSlot slot, Locality locality) {
        this.slot = slot;
        this.locality = locality;
    }

    public AllocatedSlot getSlot() { return slot; }
    public Locality getLocality() { return locality; }

    public static SlotAndLocality withLocality(
            AllocatedSlot slot,
            Locality locality) {
        return new SlotAndLocality(slot, locality);
    }
}

public enum Locality {
    LOCAL,      // 本地:Slot 所在 TM 与输入源在同一节点
    HOST_SLOT,  // 主机级:Slot 所在 TM 与输入源在同一主机
    UNKNOWN     // 未知:无法判断位置关系
}

SlotAndLocality 封装了 Slot 和位置信息(Locality),用于 Slot 亲和性调度。Locality 有三个级别:LOCAL(Slot 与输入源在同一节点,数据本地性最好)、HOST_SLOT(同一主机)、UNKNOWN(无法判断)。调度器优先选择 LOCAL 级别的 Slot,减少数据传输开销。

3.5 SchedulerNG 调度器

java 复制代码
public class DefaultScheduler implements SchedulerNG {
    private final SlotPool slotPool;
    private final ExecutionGraph executionGraph;

    private void allocateSlots(Execution execution) {
        // 为 Execution 申请 Slot
        CompletableFuture<SlotAndLocality> slotFuture =
            slotPool.requestSlot(
                execution.getJobId(),
                execution.getAllocationId(),
                execution.getResourceProfile(),
                execution.getPreferredLocation(),
                timeout);

        // Slot 分配成功后部署 Task
        slotFuture.whenComplete((slotAndLocality, throwable) -> {
            if (throwable != null) {
                // 分配失败,触发 failover
                handleSlotAllocationFailure(execution, throwable);
            } else {
                // 分配成功,部署 Task
                deployTask(execution, slotAndLocality);
            }
        });
    }

    private void deployTask(
            Execution execution,
            SlotAndLocality slotAndLocality) {
        // 获取 TaskManagerGateway
        TaskManagerLocation location =
            slotAndLocality.getSlot().getTaskManagerLocation();
        TaskExecutorGateway taskExecutorGateway =
            taskManagerLocations.get(location.getResourceID());

        // 发送 submitTask 请求
        taskExecutorGateway.submitTask(
            execution.getTaskDeploymentDescriptor(),
            execution.getAttemptId(),
            timeout);
    }
}

SchedulerNG 是调度器,负责管理 Execution 的 Slot 分配和 Task 部署。allocateSlots 方法调用 slotPool.requestSlot() 申请 Slot,通过 CompletableFuture 异步等待分配结果。分配成功后调用 deployTask(),获取 TaskExecutorGateway,发送 submitTask 请求部署 Task。分配失败时触发 failover 策略。


四、Slot Sharing 与超时重试

下面这张图是 Slot Sharing 与超时重试,包括 Slot Sharing 机制对比、超时重试流程、关键配置和最佳实践。

Slot Sharing 机制

Slot Sharing 是 Flink 的重要资源优化机制,默认开启。开启 Slot Sharing 时,同一个 Job 的不同算子的 Task 可以共享一个 Slot。例如 Source、Map、KeyBy、Sink 四个算子的 Task 可以共享一个 Slot,只需要 1 个 Slot 即可运行。关闭 Slot Sharing 时,每个 Task 独占一个 Slot,需要 4 个 Slot。

开启 Slot Sharing 的优点:

  • 提高资源利用率,减少所需 Slot 数量
  • Pipeline 模式下数据在内存中直接传递,性能更好
  • 适合大多数流处理作业

关闭 Slot Sharing 的优点:

  • 资源隔离更好,单个 Task 异常不影响其他 Task
  • 适合内存密集型或需要独立 JVM 资源的场景
  • 缺点是资源利用率低,需要更多 Slot

超时重试机制

Slot 请求超时机制通过 slotmanager.request-timeout(默认5分钟)控制。超时重试流程:

  1. 发送请求:SlotPool 向 RM 发送 requestSlot,创建 PendingRequest 记录创建时间。
  2. 等待响应:PendingRequest 等待 Slot 分配,CompletableFuture 未完成。
  3. 超时检测:SlotManager 定期检查 PendingSlotRequest,超过 request-timeout 未分配则标记超时。
  4. 取消请求:SlotManager 取消超时的 PendingSlotRequest,通知 SlotPool 分配失败。
  5. 作业失败/重试:SlotPool 的 PendingRequest.completeExceptionally(),SchedulerNG 捕获异常,触发 failover 或 restart-strategy 重试。

关键配置

yaml 复制代码
# 调度器类型
jobmanager.scheduler: adaptive

# Slot 请求超时
slotmanager.request-timeout: 5min

# 每个 TaskManager 的 Slot 数
taskmanager.numberOfTaskSlots: 1

# 故障恢复策略
jobmanager.execution.failover-strategy: region

# 重启策略类型
restart-strategy.type: fixed-delay

# 固定延迟重启次数
restart-strategy.fixed-delay.attempts: 3

# 固定延迟重启间隔
restart-strategy.fixed-delay.delay: 10s

关键配置说明:

  • jobmanager.scheduler:调度器类型,adaptive 支持动态调整并行度,根据可用 Slot 数自动调整。
  • slotmanager.request-timeout:Slot 请求超时,默认5分钟,大作业可调大到10-15分钟。
  • taskmanager.numberOfTaskSlots:每个 TM 的 Slot 数,决定可同时执行的 Task 数。
  • jobmanager.execution.failover-strategy:故障恢复策略,region 只重启受影响的 Task 区域。
  • restart-strategy:重启策略,fixed-delay 固定延迟重试,failure-rate 失败率重试。

五、常见问题与最佳实践

常见问题

  1. Slot 请求超时

    • 原因:资源不足,无空闲 Slot,新容器启动慢。
    • 解决:增加 TM 数量,调整并行度,调大 request-timeout,检查资源配置。
  2. Slot 分配失败

    • 原因:RM 无响应、网络异常、fencing token 不匹配。
    • 解决:检查 RM 状态,确认网络连通,查看 JobManager 日志,检查 HA 配置。
  3. Slot 泄漏

    • 原因:作业异常结束时 Slot 未正确释放,allocatedSlots 持续增长。
    • 解决:监控 allocatedSlots 数量,检查作业取消逻辑,确保 Slot 释放,重启 JM 恢复。
  4. Slot 亲和性失效

    • 原因:targetAddress 配置错误、Slot 不足无法满足本地性要求。
    • 解决:检查输入源位置配置,确保足够 Slot,监控 Locality 级别,接受 UNKNOWN 降级。
  5. Slot Sharing 导致 OOM

    • 原因:多个内存密集型 Task 共享一个 Slot,总内存超出限制。
    • 解决:关闭 Slot Sharing,增加每个 Slot 的内存,调整 Slot Sharing Group 配置。

最佳实践

  1. 默认开启 Slot Sharing:大多数流处理作业开启 Slot Sharing,提高资源利用率,Pipeline 模式下数据在内存中直接传递,性能更好。只有内存密集型或隔离性要求高的场景才关闭。

  2. 合理配置 Slot 数:根据 CPU 核数配置 numberOfTaskSlots,通常 1-4 个。Slot 数过多会导致每个 Slot 分配的内存和 CPU 减少,影响 Task 执行性能;过少则资源利用率低。

  3. 配置合适超时时间:大作业可调大 request-timeout 到 10-15 分钟,避免资源申请频繁超时导致作业失败。但不要过大,否则资源不足时作业长时间挂起,影响集群稳定性。

  4. 配置重启策略:生产环境配置 fixed-delay 或 failure-rate 重启策略,Slot 分配失败时自动重试,提高作业稳定性。fixed-delay 适合稳定环境,failure-rate 适合波动较大的环境。

  5. 监控 Slot 申请状态:监控 pendingRequests 数量和等待时间,及时发现资源不足和 Slot 泄漏问题。allocatedSlots 持续增长可能存在 Slot 泄漏,availableSlots 持续为 0 说明资源不足。

  6. 使用 adaptive 调度器:adaptive 调度器支持动态调整并行度,根据可用 Slot 数自动调整,提高资源利用率和作业稳定性。适合资源波动较大的环境。

  7. 合理配置 failover 策略:region 故障恢复策略只重启受影响的 Task 区域,而不是整个作业,减少故障恢复时间和资源消耗。适合大多数流处理作业。


六、总结

Flink SlotPool 申请资源是一个6步流程:SchedulerNG 请求 Slot → SlotPool 检查可用 Slot → 向 ResourceManager 申请 → 等待 RM 分配响应 → 接收 Slot 并分配 → 部署 Task 执行。核心类包括 SlotPoolImpl(Slot 池实现,管理可用/已分配/等待请求)、PendingRequest(等待中的 Slot 请求,异步通知分配结果)、AllocatedSlot(已分配的 Slot,记录位置和资源信息)、SlotAndLocality(Slot 与位置信息封装,支持亲和性调度)、SchedulerNG(调度器,管理 Slot 分配和 Task 部署)。

Slot Sharing 是 Flink 的重要资源优化机制,默认开启,允许同一个 Job 的不同算子 Task 共享一个 Slot,提高资源利用率。关闭 Slot Sharing 可以获得更好的资源隔离,但需要更多 Slot。Slot 请求超时机制通过 slotmanager.request-timeout(默认5分钟)控制,超时后取消 PendingRequest,触发 failover 或重启策略。

理解 SlotPool 申请资源的源码实现,有助于排查 Slot 请求超时、分配失败、Slot 泄漏、亲和性失效等常见问题,为 Flink 作业的稳定运行提供保障。合理配置 Slot Sharing、超时时间、重启策略和监控指标,可以显著提高作业的稳定性和资源利用率。

相关推荐
yydbjx2 小时前
数据驱动的正畸:黄岩隐形矫正方案的数字化流水线
大数据
q243622 小时前
2026 企业 AI 办公工具选型指南:如何匹配业务场景完成端到端任务
大数据·人工智能
智商网输送线配件2 小时前
自动化流水线配件采购实战:从应急补货到非标定制的供应链优化方案
大数据·运维·人工智能·自动化·智商网·流水线设备配件
q243623 小时前
2026 企业 AI 办公工具选型指南:企业团队 AI 平台评估与场景适配
大数据·人工智能
龙亘川3 小时前
系统监控业务建模:从运行状态监测到异常处置恢复闭环
大数据·人工智能·智慧城市·开源软件·数据可视化
枯木◊靠推文躺平版3 小时前
2026企业AI办公工具选型指南:单点工具与 Work Agent 如何取舍
大数据·人工智能
MiYi124063 小时前
2026深度体验:智能体办公工具助力企业协同效率提升实测
大数据·人工智能
198******126343 小时前
2026年企业AI办公工具选型指南:如何匹配业务场景完成评估
大数据·运维·人工智能
hgfsjk3 小时前
Temu Y2 利润计算器:从定价公式到店铺利润落地
大数据·经验分享·笔记·其他