这是我参与「第四届青训营」笔记创作活动的第20天
1、 YARN 概述
1.1 初识调度系统
1.1.1 场景导入
首先考虑如下虚拟场景,如何进行调度在保障就餐公平性的前提下让尽可能多的学生都能够在该餐厅就餐、尽可能多的座位被有效使用?
- 学校为改善学生生活新建了一所美食餐厅,餐厅座位有限且只能堂食;
- 各个学院需要缴纳一定管理费用后其学生才能在该餐厅用餐,缴纳费用与分配的座位数成正比;
- 因餐厅物美价廉、环境干净,来该餐厅就餐的人络绎不绝;
1.1.2 一种简易的分配模型
- 各个学院:获得固定座位数
- 学院学生:按照学院组织
- 餐厅经理:分配餐厅座位
- 餐厅座位:有序排列放置
1.1.3 优化的分配模型
-
保障公平性
- 学院间按照分配座位满足率排序;
- 学院内先来先服务、后来后服务;
-
保障高效性
- 餐厅经理有很多助手以辅助其派发,助理可以统计就餐信息、学院排序、引导等;
- 餐厅经理做决策,助手异步同步信息;
-
保障高可用
- 两个备用经理,随时可以工作;
-
如何满足 “尽可能多”?
- 座位超售:所有学院分配座位大于总座位数
- 学院超用:有座位时学院可以超分配座位
- 座位超发:“允许一个座位坐两个人”
- 鼓励快餐:鼓励大家快速就餐离开
-
就餐学生有个性化需求
- 同用餐小组必须坐一个桌子
- 同用餐小组必须坐不同桌子
- 用餐小组必须要坐靠窗位置
- 有重要活动同学需靠前就餐
1.2 调度系统演进
1.2.1 调度系统发展的背景
- IT 到 DT 时代的变革,注重数据价值
- 数据计算方式的变革,注重计算效率
- 企业对外服务需数以万计的硬件资源
- 灵活调度、提高利用率是降本增效的关键问题
1.2.2 调度系统解决的问题
- 当用有限资源解决有限资源无法满足的需求时就需要调度
- 调度系统主要解决资源请求和可用资源间的映射问题
1.2.3 调度系统预达的目标
- 严格的多租户间公平、容量保障
- 调度过程的高吞吐与低延迟
- 高可靠性与高可用性保障
- 高可扩展的调度策略
- 高集群整体物理利用率
- 满足上层任务的个性化调度需求
- 任务持续、高效、稳定运行
- We want to have all of them… However…
1.2.4 调度系统范型
- 集中式:融合资源管理调度和任务控制
- 两层式:资源管理调度和任务控制解耦
- 共享状态式:多个调度器基于乐观并发共享全局资源视图
- 分布式:多个调度器基于先验知识进行最快调度决策
- 混合式:多种类型调度器共存,共同分配
1.3 YARN 设计思想
1.3.1 演化背景
Hadoop 1.0 时代:由分布式存储系统 HDFS 和分布式计算框架 MapReduce(MR v1) 组成,MR v1 存在很多局限:
- 可扩展性差:JobTracker 兼备资源管理和任务控制,是系统最大的瓶颈;
- 可靠性差:采用 master/slave 结构,master 存在单点故障问题;
- 资源利用率低:基于槽位的资源分配模型,各槽位间资源使用差异大;
- 无法支持多种计算框架:只支持 MR 任务,无法支持其他计算框架;
Hadoop 2.0 时代:解决了 Hadoop 1.0 时代中 HDFS 和 MR 中存在的问题:
- YARN(MR v2) 在 MR v1 的基础上发展而来,将资源管理和任务控制解耦,分别由 Resource Manager 和 ApplicationMaster 负责,是一个两层调度系统;
- Hadoop YARN(Yet Another Resource Negotiator) 支持多种计算框架的统一资源管理平台;
1.3.2 离线调度生态
- 用户逻辑层:数据分析任务、模型训练任务等
- 作业托管层:管理各种类型上层任务
- 分布式计算引擎层:各种针对不同使用场景的计算引擎,例如:MR、Spark、Flink 等
- 集群资源管理层:YARN
- 裸金属层:众多物理节点组成
1.3.3 面临挑战
- 公平性:各租户能够公平的拿到资源运行任务
- 高性能:高调度吞吐、低调度延迟,保障资源快速流转
- 高可用:集群要具备很强的容错能力
- 单集群规模提升:原生 YARN 单集群仅支持 5K
- 高集群资源利用率
- 高任务运行质量保障
1.4 YARN 整体架构
1.4.1 系统架构
-
Resource Manager
- 整个集群的大脑,负责为应用调度资源,管理应用生命周期;
- 对用户提供接口,包括命令行接口,API, WebUI 接口;
- 可以同时存在多个RM、,同一时间只有一个在工作,RM 之间通过 ZK 选主;
-
Node Manager
- 为整个集群提供资源, 接受 Container 运行;
- 管理Contianer的运行时生命周期, 包括Localization, 资源隔离, 日志聚合等;
1.4.2 任务运行生命周期核心流程
2、 YARN 核心模块
2.1 Resource Mamager
2.1.1 整体架构
2.1.2 主要职责
RM 负责集群所有资源的统一管理和分配,接收各节点汇报信息并按照一定策略分配给各个任务;
-
与客户端交互,处理来自客户端的请求
-
启动和管理 AM,运行失败时自动重试
-
管理所有 NM,接收 NM 的汇报信息并下达管理指令
-
资源管理与调度
- 将资源按照一定方式组织起来,例如:资源池
- 将任务按照一定方式组织起来,例如:队列
- 接收来自各个 AM 的资源请求
- 按照一定分配策略将资源分配给 AM
2.1.3 RMApp 状态机
- NEW_SAVING: 收到提交的应用程序后,创建 RMAppImpl 对象并将基本信息持久化;
- ACCEPTED:调度器接受该应用程序后所处的状态,任务等待被分配资源;
- RUNNING:任务成功获取到资源并在节点运行
2.1.4 RMAppAttempt 状态机
- SCHEDULED: 通过 Scheduler 合法性检查后所处的状态,开始为该 App 分配资源;
- ALLOCATED_SAVING:收到分配的 Container 后,在持久化完成前所处的状态;
- ALLOCATED:信息持久化完成后所处的状态;
- LAUNCHED:RM 的 ApplicationMasterLauncher 与 NM 通信以启动 AM 时所处的状态;
2.1.5 RMContainer 状态机
- RESERVED: 开启资源预留时,当前节点不能满足资源请求时所处的状态;
- ALLOCATED:调度器分配一个 Container 给 AM;
- ACQUIRED:AM 获取到分配的 Container 后所处的状态;
- EXPIRED: AM 获取到 Container 后,若在一定时间内未启动 Container,RM 会强制回收该 Container;
2.1.6 RMNode 状态机
- DECOMMSIONED: 节点下线后的状态;
- UNHEALTHY:节点处于不健康状态,健康检测脚本异常或磁盘故障;
- LOST:超过一定时间(默认 10 分钟)未与 RM 发生心跳后所处的状态;
2.1.7 任务/资源组织
- 任务按队列组织
- 节点按 Label 组织
2.1.8 调度流程
YARN:调度流程由心跳触发
- AM 定期与 RM 保持心跳,并将资源请求记录在 RM 中;
- 触发时机: 由节点心跳触发针对此节点的调度;
- 找 Label: 根据节点 Label 找到对应 Lable 下的所有队列;
- 找队列: 将队列进行 DRF 排序, 找到当前最“饥饿”的队列;
- 找应用: 优先级高任务优先, 优先级相同时按 DRF 算法排序;
- 找资源请求: 优先级最高的资源优先请求进行资源分配;
2.1.9 典型调度器
2.2 Node Manager
2.2.1 整体架构
2.2.2 主要职责
NM 是节点代理,从 AM 接受命令(启停 Container)并执行,通过心跳方式向 RM 汇报节点状态并领取命令(清理 Container)。
-
与 RM 交互
- 心跳汇报节点健康状况和 Container 运行状态;
- 领取 RM 下达的命令;
-
与 AM 交互
- 启动容器
- 停止容器
- 获取容器状态
2.2.3 Application 状态机
- INITING: Application 初始化状态,创建工作目录和日志目录;
- FINISHING_CONTAINERS_WAIT:调等待回收 Container 所占用的资源所处的状态;
- APPLICATION_RESOURCE_CLEANINGUP:Application 所有 Container 占用的资源被回收后所处的状态;
2.2.4 Container 状态机
- LOCALIZING: 正在从 HDFS 下载依赖的资源;
- EXITED_WITH_SUCCESS:Container 运行脚本正常退出执行;
- CONTAINER_CLEANUP_AFTER_KILL:Container 被 kill 后所处的状态;
2.2.5 LocalizedResource 状态机
- DOWNLOADING: 资源处于下载状态;
- LOCALIZED:资源下载完成;
- FAILED:资源下载失败;
2.2.6 节点健康检查机制
节点健康检测机制是 NM 自带的健康状况诊断机制。通过该机制 NM 可时刻掌握自己健康状况并及时汇报给 RM,RM 根据 NM 健康情况决定是否为其分配新任务。
-
自定义 Shell
- NodeHealthScriptRunner 服务周期性执行节点健康状况检测脚本;
- 若输出以 “ERROR”开头,节点处于 unhealthy 状态并随心跳上报给 RM,RM 拉黑节点并停止分配新任务;
- 脚本一直执行,一旦节点变为 healthy 状态,RM 会继续为该节点分配新任务;
-
检测磁盘损坏数目
- LocalDirsHandlerService 服务周期性检测 NM 本地磁盘好坏,一旦发现正常磁盘比例低于一定阈值则节点处于 unhealthy 状态;
- NM 判断磁盘好坏的标准:如果一个目录具有读、写和执行权限,则目录正常;
3、 YARN 重要机制
3.1 调度策略
3.1.1 Fair Share 调度策略背景
什么是 Fair Share 调度策略?
- 队列配置 minShare 和 maxShare,当队列空闲时按照一定策略将资源分配给其他活跃队列;
为什么需要 Fair Share 调度策略?
- 保障公平的前提下实现队列间资源共享,提高资源利用率,缓解繁忙队列压力;
Fair Share 类型:
- Steady Fair Share (Total Resource * S.weight)
- Instantaneous Fair Share
3.1.2 Instantaneous Fair Share 定义
-
定义
- 所有队列 Fair Share 之和 <= TotalResource;
- S.minShare <= Fair Share <= S.maxShare;
-
目标
- 找到一个 R 使其满足:
- R * (All S.wieght)<= TotalResource;
- S.minShare <= R * S.weight <= S.maxShare;
-
结果
- 若 S.minShare > R * S.weight, Fair Share = S.minShare
- 若 S.maxShare < R * S.weight,Fair Share = S.maxShare
- 其他 Fair Share = R * S.weight
3.1.3 Instantaneous Fair Share 计算逻辑
-
计算 Total Resource
-
初始化 R 上限 RMax
- 获取所有 non-fixed Schedulable 的 maxShare
- 初始化 R 为 1,每次翻倍
- 直到所有 Schedulable 分完所有资源
-
通过二分法寻找 R [0,RMax]
- mid = (left + right) / 2.0
- 若 plannedResourceUsed == totalResource,right = mid;
- 若 plannedResourceUsed < totalResource,left = mid;
- 若 plannedResourceUsed > totalResource,right = mid;
-
计算 Fair Share
- 若 S.minShare > right * S.weight, Fair Share = S.minShare;
- 若 S.maxShare < right * S.weight, Fair Share = S.maxShare;
- 其他情况 Fair Share = right * S.weight
3.1.4 DRF(Dominant Resource Fair) 调度策略
为什么需要 DRF 调度策略?
- 在保证公平性的前提下进行资源降维,以达到更好的分配效果;
什么是 DRF 调度策略?
- DRF 是最大最小公平算法在多维资源上的具体实现;
- 旨在使不同用户的“主分享量”最大化的保持公平;
最大最小公平算法:最大化最小资源需求的满足度
- 资源按照需求递增的顺序进行分配;
- 用户获取的资源不超过自身需求;
- 对未满足的用户,等价分享剩余资源;
3.1.5 DRF 调度策略描述
-
场景:系统有 <9CPU, 18G>,A 每个任务需要 <1CPU,4G>,B 每个任务需要 <3CPU,1G>,对于 A 因为:1/9 < 4/18,所以 A 任务的主资源是内存,B 任务的主资源是 CPU;
-
数学模型:一定约束条件下的最优化问题,下面 x 代表 A 任务的个数,y 代表 B 任务的个数
-
最大化资源分配 max(x,y)
-
约束条件:
- (x+3y)<=9(CPU约束);
- (4x+y)<= 18(内存约束);
- 4x/18 == 3y/9(主资源公平约束);
-
-
最终计算得到: x=3,y=2
3.1.6 DRF 调度策略计算逻辑
- R 表示总资源量,有 m 个维度
- C 已经使用的资源量
- si 用户 i 的“主分享量”
- Ui 分配给用户 i 的资源量
-
选择最小“主分享量”用户 i
-
Di 用户 i 下一个任务资源需求量
-
若资源充足
- 更新已使用资源量
- 更新用户 i 的已分配资源量
- 更新用户 i 的“主分享量”
3.2 事件机制
3.2.1 状态机管理
- 状态机由一组状态(初始状态、中间状态和最终状态)组成,状态机从初始状态开始运行,接收一组特定事件,经过一系列中间状态后,到达最终状态并退出;
- 每种状态转换由一个四元组表示:转换前状态、转换后状态、事件和回调函数;
- YARN 定义了三种状态转换方式如下所示:
3.2.2 事件处理模型
- RM 中所有处理请求都会作为事件进入系统;
- AsyncDispatcher 负责传递事件给相应事件调度器--EventHandler;
- 事件调度器可能将该事件转发给另外一个事件调度器或带有有限状态机的事件处理器;
- 处理结果也以事件形式输出,新事件会再次被中央异步调度器转发给下一个事件调度器,直至处理完成;
YARN 采用了基于事件驱动的并发模型,具有很强的并发性可提高系统性能。
3.3 容错机制 - 高可用性
3.3.1 RM 高可用
热备方案:集群中存在一个对外服务的 Active Master 和若干 Standby Master,一旦 Active Master 故障,立即采取一定策略选取某个 Standby Master 转换为 Active Master 正常对外提供服务;
基于共享存储的 HA 解决方案:Active Master 不断将信息写入共享存储系统(ZK),故障切换时 Standby Master 从共享存储恢复数据,待信息完全同步后切换至 Active Master;
两种切换模式:
- 手动模式:使用 “yarn rmadmin”命令将现在的 Active Master 切换为 Standby 并选择一个 Standby 切换为 Active Master;
- 自动模式:使用 ZK 的 ActiveStandbyElector 进行选主操作,ZK 中有一个 /yarn-leader-election/yarn1 的锁节点,所有 RM 在启动时去竞争写一个 Lock 子节点:/yarn-leader-election/yarn1/ActiveBreadCrumb,该节点是临时节点。ZK 保证最终只有一个 RM 能够创建成功,创建成功的为 Active Master;
Client、AM、NM 自动重试:切主时各组件基于配置文件中的所有 RM 采用 round-robin 轮询方式不断尝试连接 RM 直到命中 Active Master;
3.3.2 NM 高可用
- 相关信息存储至 leveldb 数据库;
- NM 重启时加载 yarn-nm-recovery 下的 leveldb 数据库;
4、 公司实践
4.1 Gang 调度器
4.1.1 为什么要开发 Gang 调度器?
流式作业和训练作业的调度需求与批处理有很大的不同
- 批处理强调高吞吐
- 流式/训练类型作业更强调低延迟和全局视角
- 调度缺乏全局视角
- 单个 Application 调度过慢
- Application 之间存在资源互锁情况
4.1.2 Gang 调度器有什么典型特点?
-
全局视角: 增加 YARN 调度时的全局视角
- 首先支持 Flink/GPU 训练的全局约束需求(负载均衡/ GPU 亲和性)
- 为未来更丰富的全局约束留出扩展空间
-
低延迟:
- 对于低 latency 需求的申请,在集群可以满足资源的条件时,1 次申请直接返回资源(ms级别)。
-
Gang性交付:
- 调度提供 all-or-nothing 的语义, 对于无法满足的申请直接返回失败, 对于可以满足的申请直接交付所有的申请资源,规避应用之间资源互锁的情况。
4.1.3 Gang 调度器调度流程
-
选择 App
- 基于公平性策略对所有队列排序并选择一个队列;
- 基于公平性策略对队列内的任务排序并选择一个任务;
-
分配资源
-
强约束阶段:过滤掉不符合条件的节点
-
弱约束阶段:选择合适的节点分配资源(不排序,时间复杂度 O(n))
- Quota 平均:分配后节点已使用资源尽可能平均。总请求资源为 V1,总节点数为 N,已用资源为 U,节点目标资源为:S = (V1 + U)/N,遍历所有节点,每个节点分配 S - Un 即可;
- 跳过高 load 节点:优先往低 load 节点调度。满足 load 阈值节点 N1,不满足 N2,优先把 N1 剩余资源分配完,分配后未满足资源量为 V2,每个节点分配 V2/N2;
- 兜底分配
-
4.2 反调度器
4.2.1 为什么需要开发反调度器?
-
调度器的调度决策受“时空”限制
- “时”表示触发调度时的时刻
- “空”表示触发调度时集群的状态
-
任务运行和集群状态高动态性
- 任务资源使用随流量变化而不断波动
- 调度过程持续进行,集群状态持续变化
-
需要持续保证最初调度决策的正确性
4.2.2 反调度器调度流程
- 根据 AM 请求中的强约束,构造强约束集;
- 遍历强约束集选择不再符合强约束条件的节点;
- 遍历异常节点下的 Container,针对每个节点选择需要进行反调度的 Container,并添加至反调度 Container 列表中;
- 将反调度 Container 列表随心跳返回给对应AM;
4.2.3 反调度器与 Gang 调度器关系
-
两者不同点
- 发挥作用的时机不同。调度器在资源请求到来时根据当前时刻集群信息进行资源分配。反调度器在任务运行过程中,检测出不再正确的调度决策并进行修正。
- 处理机制相反。调度器负责根据调度策略分配container到计算节点。反调度器负责根据反调度策略清理计算节点上的container。
-
两者联系
- 反调度器是调度器的修正和补充,在反调度器的恢复过程中需要通过调度器进行新container的申请,两者共同促进资源的合理分配。
反调度器是 Gang 调度器的“伴侣”
4.3 单集群规模突破 50K
4.3.1 为什么需要提升单集群规模?
-
更好的资源池化和资源共享
- 资源池更大,有利于资源的分时复用和共享;
- 资源高效共享可以提高集群整体资源利用率;
-
降低运维成本
- YARN 原生系统单集群仅支持 5K 节点;
- 每多一个集群,运维负担就会加重;
4.3.2 RPC 瓶颈
-
RPC 层: 接收请求、处理请求、返回结果
- RPC 处理时间 5ms,handler 默认 80 线程
- 消费吞吐约 16K/s,RPC server queue 容易打满
4.3.3 Dispatcher 瓶颈
-
Dispatcher 层:将事件传递给对应的事件调度器
- 生产速率过大,AsyncDispatcher queue 容易 pending
- 消费速率过低,Scheduler 的 NODE_UPDATE 事件处理过慢
4.3.4 Scheduler 瓶颈
-
Scheduler 层:真正调度
- FSLeafQueue 分配单 Container 延迟太高,存在空转
4.3.5 心跳反压机制
将 NM 节点的心跳机制改为根据 RM 的压力动态调整,当事件池中的事件超过阈值时调大心跳周期,当事件池中的事件小于阈值时调小心跳周期
4.3.6 多线程调度
收到调度事件后,对节点按 hashcode 放到对应的 scheduler queue 即返回
4.3.7 其他优化
- 事件精简:对 YARN 内部事件梳理调整, 精准修改了一些事件处理逻辑
- 空转优化:调度时过滤不需要资源的 App,减少空转
- 内存单位优化:修改内存单位 (int->long) 突破单个集群 21亿 MB 限制
- 切主优化:通过对切主过程进行深度优化, 将切主时间控制在秒级