[业界方案] Yarn的业界解决方案和未来方向
0x00 摘要
本文通过分析学习两篇文章来看目前工业界可能偏好的解决方案,也顺便探寻下Yarn的未来发展方向。
0x01 Yarn
yarn是集群资源管理层,分成了三个模块:
ResourceManager 管理整个集群的资源;
- 整个集群的大脑,负责为应用调度资源,管理应用生命周期。
- 对用户提供接口,包括命令行接口,API, WebUI 接口。
- 可以同时存在多个 RM,但同一时间只有一个在工作,RM 之间通过 ZK 选主。
NodeManager 管理整个机器资源情况;
- 为整个集群提供资源,接受 Container 运行。
- 管理 Contianer 的运行时生命周期,包括 Localization,资源隔离,日志聚合等。
ApplicationMaster管理整个APP的资源信息。MR/SPARK/Flink实现了自己的AM逻辑在yarn上运行。
1.1 参考文章
主要是参考下面两篇文章,个人觉得有代表性,可以管中窥豹。下面对此两篇文章的内容一律以引用格式。
Yarn 在快手的应用实践与技术演进之路
YARN 在字节跳动的优化与实践
0x02 分析
2.1 综述
可以看出来两个公司应用领域都差不多:离线作业/流式作业/模型训练三大场景。
快手主要是:根据不同领域做不同定制。
字节跳动是:没有针对领域做不同定制(当然也可能是没有披露)。
2.1.1 yarn在快手的应用特点
yarn上面服务了一些经典的离线计算,像HiveSQL,最终回变成一个MR/SPARK作业或者PESTO查询在yarn上运行。 对于流式的实时数据处理需求,我们上层有一个青藤平台来托管FLINK在YARN上运行。 对于模型训练的场景,我们是用XLearning作为调度器,调度TensorFlow,XGBoost,MPI等训练学习引擎。 基于Spark和XLearning,我们打造了亚瑟机器学习平台,可以把数据处理流程和一些模型的训练、预测流程做一个打通,方便用户使用。
2.1.2 字节跳动 YARN 应用特点
字节跳动的 YARN 是在 16 年从社区当时最新的 2.6.0 版本中 fork 出来的,主要承载着公司内的离线作业/流式作业/模型训练三大场景。由于公司内的 YARN 服务规模巨大、场景复杂,遇到了各种问题,在社区版本没有提供解决方案之前,内部研发同学定制了许多内容来解决具体问题,经过 4 年来上千次的修改,公司内的版本已经跟社区的版本相差较大。
2.2 关键定制
可以看到:稳定性,调用性能和利用率是两个公司都注重的。
此外快手提到了 “小IO优化”。字节跳动提到了“异地多活”。
2.2.1 yarn在快手的应用特点
对yarn的一些改动,主要分成四个方面: (1)集群稳定性方面的优化。 (2)对yarn的抢占机制做了优化。 (3)yarn的调度性能提升。 (4)计算集群小IO优化。
2.2.2 字节跳动 YARN 应用特点
这些关键定制主要包括四个方面:
- 稳定性提升: 包括摆脱对 HDFS 强依赖, Container 分级与驱逐, 非受控 Container 管理。
- 利用率提升: 包括分配率提升和物理使用率提升。
- 多种负载场景优化: 包括批处理 / 流式 / 模型训练 三种场景下的体验提升。
- 异地多活: 包括统一的 YARN Client 和 UI 等内容。
2.3 具体定制
下面把两个公司具体优化点整合起来介绍。
2.3.1 事件处理
Yarn中几个模块之间有大量的事件传递和处理,其中势必有缺陷和可优化之处,这就是业界优化之处,比如优化冗余,解耦合。具体体现在速度提升,对IO操作优化,集群启动上。
- 事件处理是单线程的,会有各种各样的问题,整个事件处理压力非常大。针对这些问题我们做了一些针对性的优化。
- 通过对 YARN 内部事件梳理调整,精准的修改了一些事件处理逻辑。
- 把处理NM节点信息的汇报等操作、抽离出来,放在一个额外的线程上。
- 将 NodeManager 节点的心跳机制改为根据 ResourceManager 的压力动态调整。
- RM和NM交互有一些冗余事件的,我们对冗余事件进行了一些优化。对于NM,我们设计了一个慢启动的策略,如果NM刚启动没有必要维持每秒汇报一次,开始可以20秒汇报一次,下次10秒,下次5秒,最终恢复到正常,这样会把整个RM的事件处理压力降下来。
- 优化事件处理的耗时操作:
- RM对HDFS的操作主要集中在失败APP的处理,不是非常核心的逻辑,解决方案是把HDFS的操作从同步改成异步。
- 把像DNS的操作这种比较重IO的操作进行相应的优化,确保事件处理逻辑中都是快速的CPU操作,保证事件处理的高效。
2.3.2 调度方面
Yarn有三种调度器,都不能满足两个公司的需求,两个公司都对调度做了优化,或者干脆重新写了调度器。
- yarn的调度模型各种排序要耗费很大资源。减少排序时间从三方面着手。
- 减少排序规模。实际场景中大部分是不需要资源和参与排序的,这样把整个排序规模减小了。
- 减少单次排序时间,
- 优化排序算法。在yarn里面每一次compare的时候有很多可以可以优化的地方,比如计算一个队列使用的资源量,有一些临时对象可以cache住,最终缩小整个单次排序的时间。Collection.sort底层使用归并排序,我们改成堆排序。
- 为了提高调度的扩展性,我们重写调度逻辑,开发了Kwaischeduler。
- 底层实现主要分成两部分。一部分是集群资源的预分配过程,把一些资源分配到每个APP。第二部分是APP怎么去每台机器上竞争资源。
- 一个单独线程定期会对集群情况做snapshot,基于snapshot来做一个上帝视角的资源分配。我们为每个APP分配出资源之后,就可以把APP丢到线程池里面并发抢资源,对相应的节点排序,选出分数最高的节点,最终你会有一个commit的过程,真正拿到资源。整个调度分配结束后,会把整个分配结果写回到整个原生的yarn框架。
- 处理很多的小IO问题。很多的小IO,导致整个集群磁盘util非常高,但是磁盘读写速度非常慢。针对这个问题,我们对MR的shuffle过程做一个Cache。在shuffle过程中,当一个请求来的时候,我们分析一下这次shuffle过程有没有可能产生比较多小IO,可以按需把shuffle数据放到cache里,只需要一次大的IO把数据搬到Cache里面,后面的shuffle请求可以直接从cache里面读,消灭了后面多次小IO,通过这个我们优化,提升了整个的集群IO性能。
- 社区原生版本的 FairScheduler 是单线程的,在节点数量较多时,是整体集群最大的瓶颈。我们通过将 FairScheduler 改造为并发的多线程版本,并将调度器内部的锁拆分为更加细粒度的读锁和写锁,将调度吞吐提升 7 倍以上。
2.3.3 资源方面
从现实看,资源的隔离和利用都有缺陷,所以两个公司都做了细化、优化。
- 现在社区主要是用cgroup做一些内存和CPU的隔离,其他方面的隔离非常弱的,我们当时碰到一些场景比如磁盘打满了,FD泄露、线程泄露的问题。解决方案就是对container的线程数目,磁盘大小定期检查,如果超过阙值,直接kill掉。
- 除了开启 YANR 原生默认支持的 CGroup 限制之外,我们还配置了更加丰富的 CGroup 管理策略,比如在 share 模式下支持自定义的最大值限制,支持绑核,支持绑 NUMA 节点等. 通过这些措施,给流式作业和训练作业更加灵活的管控策略,满足不同场景下的隔离或共享需求。
- 原生的 YARN 中,用户申请的资源和实际使用的资源经常会出现比较大的偏差, 导致出现大量的资源浪费的情况,为此我们开发了一整套的资源动态调整方案,可以将申请的资源调整到接近于实际使用资源的数值。
- 在实际使用中发现,如果资源调整必须以一个核为最小粒度的话,还是会出现很严重的浪费,比如用户真实的需求可能是 0.001 个核*1000,原生的 YARN 只能分配 1000 个核,就白白浪费了 999 个核。我们开发了以千分之一核为最小粒度的功能,可以有效的减少资源的浪费。并且千分之一核与资源动态调整结合,可以更加精细化的调整资源。
- 原生的 YARN 在调度时只考虑资源是否满足,经常会出现一个节点 CPU 被打满,但是内存还有剩余的情况。我们引入节点 DRF(Dominant Resource Fairness)机制,计算每个节点的剩余资源的主资源,当调度的 Task 的主资源与节点的主资源不匹配时,先延迟此次调度,直到一定次数后再放松约束。
2.3.4 稳定性
这点和下面一点属于稳定性方面。
- 将 HDFS 做成弱依赖
-
- 对于一般的离线批处理来说,如果 HDFS 服务不可用了,那么 YARN 也没必要继续运行了。但是在字节跳动内部由于 YARN 还同时承载流式作业和模型训练,因此不能容忍 HDFS 故障影响到 YARN。为此,我们通过将 NodeLabel 存储到 ZK 中,将 Container Log 在 HDFS 的目录初始化和上传都改为异步的方式,摆脱了对 HDFS 的强依赖。
- Container 分级与驱逐
-
- 某些 Container 的磁盘空间占用过高,或者将单机 Load 打得非常高,会比较严重的影响到其它 Container 的正常运行,为此,我们为 YARN 定制了 Container 分级与驱逐机制。对于可能会严重影响到其它 Container 的 Container 会进行主动驱逐。对于被驱逐的作业,可申请到独立的 Label 中运行。
- 非受控 Container 的清理机制
-
- 由于种种原因,线上总是会出现一些 Container 明明还在运行,但是已经不受 YARN 的管控。为此我们在 YARN 的 NodeManager 中增加了非受控 Container 的清理机制。
2.3.5 单点问题
- 扩展了NM磁盘的黑名单功能,通过container的失败信息做一些规则匹配,这样可以定向发现一些磁盘问题,把疑似有问题的磁盘放在黑名单里面,不再向这个磁盘调度作业。
- 社区提出了AM的黑名单机制,主要来解决AM的失败问题,如果AM大量失败,不往这台机器上调度AM,APP内部依赖自己的黑名单机制,发现这些问题机器。我们觉得这样可能会造成很多无效的container失败,所以我们的解决思路是建立整个集群的黑名单,而不单独是AM的黑名单。当我们通过一些规则发现有大量的container在某一台机器失败,或者这台机器的container调度速度非常异常,我们会把这台机器放到我们集群的黑名单里面,不再向这台机器调度资源。
- 离线批处理场景经常会遇到"Fetch Failed"的问题,主要来源是本地的磁盘 IOPS 不足,导致 Shuffle Service 卡住,为了缓解这个问题,我们在资源调度的过程中加入目标主机 LoadAvg 的考虑因素,如果一台机器的 LoadAvg 过高,则暂时跳过对其分配新任务. 通过这个机制,将"Fetch Failed"问题降低了约 40%。
2.3.6 与流式&在线服务混部
本部分和下一部分都是为了特殊场景做改造。
- 通过将 NodeManager 改造为可以根据宿主机的富余资源动态的调整的 NM',来达到与流式作业和在线服务的混部,为离线提供更多资源的目的。
- 为了弥补原生 YARN 在低延迟和全局视角上的缺陷,我们开发了一个全新的调度器 Gang Scheduler。
- Gang Scheduler 提供了一个 All-or-Nothing (一次全交付或不交付)的语义,如作业申请 1000 个 container,那么要么直接返回 1000 个 container,要么就返回失败,并提示失败的原因。这样可以有效的避免两个作业都只拿到一半的资源,谁也无法启动的互锁局面。
- Gang Scheduler 还有个特性是超低延迟, 它可以在毫秒级给出 All-or-Nothing 的结论,这样可以大大缓解流式作业在重启时的 lag 积压问题。
- Gang Scheduler 为流式作业和训练作业提供了全局视角,每个作业可以通过配置自己定制的强约束和弱约束来达到全局最优的放置策略。其中,强约束是指必须要满足的条件;弱约束是指尽量满足,但确实无法满足时可以接受降级的约束。目前支持的强约束包括节点属性, 高负载等;支持的弱约束包括:节点属性,高负载,Container 打散,Quota 平均,GPU 亲和性等。
2.3.7 训练场景
- 为了更好的隔离性,定制了支持 GPU 和 Ceph 的 Docker
- 为了更灵活的资源申请,定制了带范围的资源值 (传统的 YARN 资源只有个数, 没有范围,比如多少个 CPU,多少 GB 内存,但在训练场景下,有时希望有范围,比如当需要两个 GPU 卡时,不止希望随意的两张卡,而是希望要一台机器上两个连号的 GPU 卡,比如卡 0 和卡 1 是连号的,而卡 0 和卡 2 不是连号的。这个场景同样也适用于端口号。)
- 为了更高效的同时使用 CPU 和 GPU 机器,定制了节点属性功能。
2.3.8 单集群规模
- 修改内存单位(int->long)突破单个集群 21 亿 MB 的限制
- 通过对切主过程进行深度优化, 将切主时间控制在秒级
0x03 yarn的未来规划
综合两个公司对具体未来规划,总结如下,基本能看出来主要思路就是:提高利用率,丰富功能,扩大应用领域。
3.1 物理利用率提升
- yarn现在主要托管的是一些离线计算的资源,公司还有很多空闲资源没有使用,怎么来使用这些空闲资源,怎么做到把一些合适的任务调入到一些比较空闲的机器上,当这个机器需要的时候,及时把任务迁移走,怎么减少业务相互的影响,底层这方面需要做什么支撑,这都需要探索。
- 构建作业分级保障,现在我们yarn的集群规模比较大,大家使用的资源都非常多,但是这些资源有没有用到真正比较重要的业务上,其实我们是有些疑问的,有多少无效的计算在里面,当然这个涉及到业务层的优化。为作业打一些作业的标签,基于这些任务的标签,以及优先级的特性,刻划整个集群资源的使用情况,为预算或者其他的技术方案提供一些技术的底层支持。
- 更加丰富的调度谓词
- 更加低延迟
3.2 多集群
我们现在单个yarn集群规模在国内是top级的,但是单集群毕竟是容量有限,我们后面会考虑多集群建设的方案,社区的federation方案在跨IDC方面有些问题,如何在业务透明的前提下,建设跨IDC集群有非常多问题需要解决。
3.3 流式和在线服务的混合部署
- 物理利用率提升
- 更好的隔离
- 更加可控的杀死率
- GPU 资源的混部