1.基本架构
资源调度器是 YARN 中最核心的组件之一,且是插拔式的,它定义了一整套接口规范以便用户可按照需要实现自己的调度器。
YARN 自带了 FIFO,Capacity Scheduler,Fair Scheduler 三种常用资源调度器,用户也可以按照接口规范编写一个新的资源调度器。
可通过参数 yarn.resourcemanager.scheduler.class 设置资源调度器的主类,默认是 Capacity Scheduler 容量调度器
YARN的资源管理器实际上是一个事件处理器,它需要处理来自外部的六种 Scheduler-EventType 类型的事件,并根据时间的具体含义进行相应的处理,六种事件含义如下:
①NODE_REMOVED:表示集群中移除了一个计算节点(可能是故障或管理员主动移除),资源调度器需要从可分配资源总量中移除相应的资源量
②NODE_ADDED:表示集群中增加了一个计算节点,资源调度器需要将新增的资源量添加到可分配的资源总量中
③APPLICATION_ADDED:表示 ResourceManager 收到一个新的 Application。通常,资源管理器需要为每个 Application 维护一个独立的数据机构,以便于统一管理和资源分配。资源管理器需要将 Application 添加到的数据结构中
④APPLICATION_REMOVED:表示一个 Application 运行结束(可能成功或失败),资源管理器需要将该 Application 从相应的数据结构中清除
⑤CONTAINER_EXPIRED:当资源调度器将一个 Container 分配给某个 ApplicationMaster 后,如果该 ApplicationMaster 在一定时间间隔内没有使用该 Container ,则资源调度器回对该 Container 进行回收后再分配
⑥NODE_UPDATE:ResourceManager 收到 NodeManager 通过心跳机制汇报的信息后,由于此时可能有新的 Container 得到释放,会触发资源分配。该事件是六个事件中最重要的事件,会触发资源调度器最核心的资源分配机制
2.资源表示模型
YARN 支持内存和 CPU 两种资源类型的管理和分配。YARN 采用动态资源分配机制,NodeManager 启动时会向 ResourceManager 注册,注册信息中包括该节点可分配的 CPU 和内存总量,相关配置参数如下:
①yarn.nodemanager.resource.memory-mb:可分配的物理内存总量,默认为 8 * 1024 MB
②yarn.nodemanager.vmem-pmem-ratio:任务使用单位物理内存量对应最多可使用的虚拟内存两,默认值是 2.1,表示每使用 1 MB的物理内存,最多可以使用 2.1 MB 的虚拟内存总量
③yarn.nodemanager.resource.cpu-vcores:可分配的虚拟 CPU 核数,默认是8。为了更细粒度地划分 CPU 资源和考虑到 CPU 性能异构性,YARN允许管理员根据实际需要和 CPU 性能将每个物理 CPU 划分为若干个虚拟 CPU。
3.资源调度模型
①双层资源调度模型
YARN 采用了双层资源调度模型:
1)第一层中,ResourceManager 中的资源调度器将资源分配给各个 ApplicationMaster
2)第二层中,ApplicationMaster 再进一步将资源分配给其内部的各个任务 YARN的资源分配过程是异步的,也就是说,资源调度器将资源分配给一个应用程序后,不会立刻 push 给对应的 ApplicationMaster,而是暂时放到一个缓冲区中,等待 ApplicationMaster 通过周期性的心跳主动来取。
在 YARN 中,资源分配过程可分为以下七个步骤:
①NodeManager 通过周期性心跳汇报节点信息
②ResourceManager 为 NodeManager 返回一个心跳应答,包括需要释放的 Container 列表的信息
③ResourceManager 收到来自 NodeManager 的信息后,会触发一个 NODE_UPDATE 事件
④ResourceManager 收到 NODE_UPDATE 事件后,按照一定的策略将该节点上的资源(步骤②中有释放的资源)分配给各应用程序,并将分配结果放到一个内存数据结构中
⑤应用程序的 ApplicationMaster 向 ResourceManager 发送周期性心跳,领取最新分配的 Container
⑥ResourceManager 收到来自 ApplicationMaster 心跳信息后,为它分配的 Container 将以应答的形式返回给 ApplicationMaster
⑦ApplicationMaster 收到新分配的 Container 列表后,会将这些 Container 进一步分配给其内部的各个任务
资源调度器资源分配示意图如下:
资源调度器关注的是步骤④中采用的策略,即如何将节点上空闲的资源分配给各个应用程序
②资源保证机制
当应用程序申请的资源暂时无法保证时,有如下两种分配方式:
1)增量资源分配:优先为应用程序预留一个节点上的资源直到累计释放的空闲资源满足应用程序需求。
缺点:资源预留会导致资源浪费,降低集群资源利用率
2)一次性资源分配:暂时放弃当前资源直到出现一个节点剩余资源一次性满足应用程序需求
缺点:可能会产生饿死现象,即应用程序可能永远等不到满足资源需求的节点出现
YARN 采用了增量资源分配机制,尽管这种机制会造成浪费,但不会出现饿死现象(假设应用程序不会永久占用某个资源,会在一定时间内释放占用的资源)
③资源分配算法
为了支持多维资源调度,YARN 资源调度器采用了主资源公平调度算法(DRF),DRF被证明非常适用于多资源和复杂需求的环境中。
在 DRF 算法中,将所需份额(资源比例)最大的资源称为主资源,而 DRF 的基本设计思想则是将最大最小公平算法主要应用于主资源上,进而将多资源维度问题转化为单资源调度问题,即 DRF 总是最大化主资源中最小的,其算法伪代码如下:
实例:
假设系统中有 9 个 CPU 和 18 GB RAM,有两个用户(或框架)分别运行了两种任务,需要的资源分别为 <1 CPU,4GB> 和 ❤️ CPU,1GB>。对于用户A,每个任务要消耗总 CPU 的 1/9(份额)和总内存的 2/9,因此 A 的主资源为内存;对于用户B,每个任务要消耗总 CPU 的 1/3(份额)和总内存的 1/18,因此 B 的主资源为 CPU。DRF将最大化所有用户的主资源,具体分配如下图:
最终,A 获得的资源量为 <3CPU,12GB>,可运行 3 个任务;B 获得的资源量为 <6CPU,2GB>,可运行 2 个任务
4.资源抢占模型
在资源调度器中,每个队列可设置一个最小资源量和最大资源量,其中,最小资源量是资源紧缺情况下每个队列需保证的资源量,而最大资源量则是极端情况下队列也不能超过的资源使用量。
资源抢占发生的原因?
资源抢占发生的原因则完全是由于“最小资源量”这一概念。通常,为了提高资源利用率,资源调度器(包括 Capacity Scheduler 和 Fair Scheduler)会将负载较轻的队列的资源暂时分配给负载重的队列(即最小资源量并不是硬资源保证,当队列不需要任何资源时,并不会满足它的最小资源量,而是暂时将空闲资源分配给其他需要资源的队列),仅当较轻队列突然收到新提交的应用程序时,调度器才进一步将本属于该队列的资源分配给它。但由于此时资源有可能正被其他队列使用,因此调度器必须等待其他队列释放资源后,才能将这些资源“物归原主”。为了防止等待时间过长,调度器等待一段时间后若发现资源未释放,则进行资源抢占。
开启抢占功能,需配置参数 yarn.resourcemanager.scheduler.monitor.enable 为 true(默认值为 false)
在 YARN 中,资源抢占可概括为如下步骤:
①监控类 ShedulingEditPolicy 探测到需要抢占的资源,将需要抢占的资源通过事件 DROP_RESERVATION 和 PREEMPT_CONTAINER 发送给 ResourceManager
②ResourceManager 调用 ResourceSheduler 的 dropContainerReservation 和 preemptContainer 函数,标注抢占的 Container
③ResourceManager 收到来自 ApplicationMaster 的心跳信息,并通过心跳应答将待释放的资源总量和待抢占 Container 列表返回给它。 ApplicationMaster 收到该列表后,可选择如下操作:
1)杀死这些 Container
2)选择并杀死其他 Container 以凑够总量
3)不做任务处理,过段时间可能有 Container 自行释放资源或者由 ResourceManager
④监控类 ShedulingEditPolicy 探测到一段时间内,ApplicationMaster 未自行杀死约定的 Container,则将这些 Container 封装到
KILL_CONTAINER 事件中发送给 ResourceManager
⑤ResourceManager 调用 ResourceSheduler 的 killContainer 函数,而 ResourceSheduler 则标注这些待杀死的 Container
⑥ResourceManager 收到来自 NodeManager 的心跳信息,并通过心跳应答将待杀死的 Container 列表返回给它,NodeManager 收到该列表后,将这些 Container 杀死,并通过心跳告知 ResourceManager
⑦ResourceManager 收到来自 ApplicationMaster 的心跳信息,并通过心跳应答将已经杀死的 Container 列表发送给它
如何使资源抢占代价最小?
资源抢占是通过杀死正在使用的资源 Container 实现的,由于这些 Container 已经处于运行状态,直接杀死 Container 会导致已经完成的资源白白浪费。为了尽可能避免资源浪费,YARN 优先选择优先级低的 Container 最为资源抢占对象,且不会立刻杀死 Container,而是将释放资源的任务留给应用程序自己:ResourceManager 将待杀死的 Container 列表发送给对应的 ApplicationMaster,以期望它采取一定的机制自行释放这些 Container 占用的资源,再将 Container 杀死;如果一段时间后 ApplicationMaster 尚未主动杀死这些 Container,则 ResourceManager 再强制杀死这些 Container
5.Capacity Scheduler 容量调度器
Capacity Scheduler 主要有以下几个特点:
①容量保证,管理员可为每个队列设置资源最低保证和资源使用上限,而所有提交到该队列的应用程序共享这些资源
②灵活性,如果一个队列中有剩余资源,可以暂时共享给那些需要资源的队列,而一旦该队列有新的应用程序提交,则其他队列释放的资源会该换给 该队列,这种资源灵活分配方式可以明显提高资源利用率
③多重租赁,支持多用户共享集群和多应用程序同时运行。为防止单个应用程序、用户或者队列独占集群中的资源,管理员可为之增加多重约束(比 如单个应用程序同时运行的任务数等)
④安全保证,每个队列有严格的 ACL 列表规定其访问用户,每个用户可指定哪些用户允许查看自己应用程序的运行状态或者控制应用程序
⑤动态更新配置文件,管理员可根据需要动态修改各种配置参数,以实现在线集群管理
Capacity Scheduler 的实现
YARN采用了三级资源分配策略,当一个节点上有空闲资源时,它会一次选择队列、应用程序和 Container(请求)使用该资源。具体步骤如下:
①选择队列
选择队列时,YARN 采用了基于优先级的深度优先遍历方法,从根队列开始,优先选择资源占用率最低的队列分配资源
②选择应用程序
默认按照提交作业的优先级和提交时间顺序分配资源
③选择 Container(请求)
按照容器的优先级分配资源;
如果优先级相同,按照数据本地性原则:
1)任务和数据在同一节点
2)任务和数据在同一机架
3)任务和数据不在同一节点也不在同一机架
6.Fair Scheduler
Fair Scheduler 与 Capacity Scheduler的相同点:
①多队列:支持多队列多作业
②容量保证:管理员可为每个队列设置资源最低保证和资源使用上线
③灵活性:如果一个队列中的资源有剩余,可以暂时共享给那些需要资源的队列,而一旦该队列有新的应用程序提交,则其他队列借调的资源会归还给该队列
④多租户:支持多用户共享集群和多应用程序同时运行;为了防止同一个用户的作业独占队列中的资源,该调度器会对同一用户提交的作业所占资源量进行限定
Fair Scheduler 与 Capacity Scheduler的不同点:
①资源公平共享,在每个队列中,Fair Scheduler 可选择按照 FIFO,Fair 或 DRF 策略为应用程序分配资源。其中,Fair 策略是一种基于最大最小公平算法实现的资源多路复用方式,默认情况下,每个队列内部采用该方式分配资源。具体来说,如果一个队列中有两个应用程序提交时,则每个应用程序可得到 1/2 的资源;如果一个队列中有三个应用程序提交时,则每个应用程序可得到 1/3 的资源
②调度策略配置灵活,Fair Scheduler 允许管理员为每个队列单独设置调度策略(当前支持 FIFO,Fair 或 DRF)
③提高小应用程序响应事件,由于采用了最大最小公平算法,小作业可以快速获取资源并运行完成
Fair Scheduler 的实现
和 Capacity Scheduler 一样,Fair Scheduler 采用了三级资源分配策略,即当一个节点上有空闲资源时,它会依次选择队列、应用程序和 Container 使用该资源,每一步都是按照公平策略分配资源,但是可能会存在延时,因为要等待资源释放