欢迎阅读美图数据技术团队的「Spark,从入门到精通」系列文章,本系列文章将由浅入深为大家介绍 Spark,从框架入门到底层架构的实现,相信总有一种姿势适合你,欢迎大家持续关注:)
往期直通车:Hello Spark!
/ 为什么需要 Yarn? /
Yarn 的全称是 Yet Anther Resource Negotiator(另一种资源协商者)。它作为 Hadoop 的一个组件,官方对它的定义是一个工作调度和集群资源管理的框架。
Yarn 最早出现于 Hadoop 0.23 分支中,0.23 分支是一个实验性分支,之后经过了几次迭代,最后发布于 2014 年 6 月的 0.23.11 版本(该分支的最后一个版本)。在 0.23.0 发布后不久的 2011 年 12 月,Hadoop 的 0.20 分支发展成了 Hadoop1.0,一直到 1.0 的最后一个版本 1.2.1-stable 都没有出现 Yarn 的身影,而在 Hadoop2.0 的第一个版本 2.0.0-alpha,Yarn 已经作为一个正式组件加入。在 2.0.2-alpha 版本,它已经支持了 2k 台机器的集群,接着在 2.0.3-alpha 版本中已经可以支持 30k 台机器的集群。在 2.0.3-alpha 版本中同时还支持了多种资源,如 cpu&memory 的调度和 ResourceManager restart。
图 1,via https://blog.csdn.net/suifeng3051/article/details/49364677
如图 1 所示, Hadoop1.0 的运作流程如下:
1.客户端提交任务给集群;
2.JobTracker 接收 Job 请求;
3.JobTracker 根据 Job 的输入参数向 NameNode 请求包含这些文件数据块的 DataNode 节点列表;
4.JobTracker 确定 Job 的执行计划:确认 Map、Reduce 的 Task 数量,并分配 Task 到离数据块最近的节点上执行。
最初,Hadoop1.0 能够很好地支撑大数据计算,但是随着计算规模的扩大和计算模型的多样化,它逐渐力不从心。众所周知当集群性能不足的时候可以简单粗暴地加机器,但 JobTracker 同时部署多个时只有一个是处于 active 状态,因此受限于这个 active JobTracker 的负载上限,整个集群能够容纳的机器也有限,有数据显示整个集群的管理上限约为 4k 台机器。同时应用程序相关和资源管理相关的逻辑全部放在 JobTracker中,当集群规模扩大的时候,会存在一个瓶颈。除此之外,Map-Reduce 计算模型与 JobTracker 的耦合过高,其他计算模型难以在 Hadoop1.0 上运行。
Yarn 是 Hadoop 基于这些问题的一个解决方案,接下来通过了解 Yarn 的组件、架构以及运作机制来分析 Yarn 是如何解决这些问题的。
/ Yarn 是什么? /
Yarn 的组件&基本架构
如图 2 所示 Yarn 采用 Master/Slave 结构,整体采用双层调度架构。在第一层的调度是 ResourceManager 和 NodeManager:ResourceManager 是 Master 节点,相当于 JobTracker,包含 Scheduler 和App Manager 两个组件,分管资源调度和应用管理;NodeManager 是 Slave 节点,可以部署在独立的机器上,用于管理机器上的资源。NodeManager 会向 ResourceManager 报告它的资源数量、使用情况,并接受 ResourceManager 的资源调度。
*ResourceManager 同 JobTracker 一样可以多机部署,并且只有一台处于 active 状态。但是在 ResourceManager 中将调度管理和应用管理作了拆分,两个组件的功能更专一。
图 2
第二层的调度指的是 NodeManager 和 Container。NodeManager 会将 Cpu&内存等资源抽象成一个个的 Container,并管理它们的生命周期。
通过采用双层调度结构将 Scheduler 管理的资源由细粒度的 Cpu&内存变成了粗粒度的 Container,降低了负载。在 App Manager 组件中也只需要管理 App Master,不需要管理任务调度执行的完整信息,同样降低了负载。通过降低 ResourceManager 的负载,变相地提高了集群的扩展性。
Yarn 运作流程
图 3,via https://blog.csdn.net/suifeng3051/article/details/49486927
如图 3 所示 Yarn 的运作流程如下:
1.客户端向 ResourceManager 的 App Manager 提交应用并请求一个 AppMaster 实例;
2.ResourceManager 找到可以运行一个 Container 的 NodeManager,并在这个 Container 中启动 AppMaster 实例;
3.App Master 向 ResourceManager 注册,注册之后,客户端就可以查询 ResourceManager 获得自己 App Master 的详情以及直接和 App Master 交互;
4.接着 App Master 向 Resource Manager 请求资源,即 Container;
5.获得 Container 后,App Master 启动 Container,并执行 Task;
6.Container 执行过程中会把运行进度和状态等信息发送给 AppMaster;
7.客户端主动和 App Master 交流应用的运行状态、进度更新等信息;
8.所有工作完成 App Master 向 RM 取消注册然后关闭,同时所有的 Container 也归还给系统。
通过这个 Job 的处理过程可以看到 App Master 是作为 Job 的驱动角色,它驱动了 Job 任务的调度执行。在这个运作流程中,App Manager 只需要管理 App Master 的生命周期以及保存它的内部状态,而 App Master 这个角色的抽象使得每种类型的应用都可以定制自己的 App Master,这样其他的计算模型就可以相对容易地运行在 Yarn 集群上。
Yarn HA(容灾备援)
接下来介绍的是 Yarn 集群高可用中关于容错备援的设计。根据图 3 所示的 Yarn 架构图,假如 Container 故障 Resource Manager 可以分配其他的 Container 继续执行,当运行 App Master 的 Container 故障后也将分配新的 Container,App Master 可以从 App Manager 获取信息恢复。当 NodeManager 故障的时候系统可以先把这个节点移除,在其他 NodeManager 重启再继续任务。
图 4,via https://www.cnblogs.com/sodawoods-blogs/p/8715231.html
那么当 ResourceManager 故障的时候呢?如上文所说的在 Yarn 集群中,ResourceManager 可以启动多台,只有其中一台是 active 状态的,其他都处于待命状态。这台 active 状态的 ResourceManager 执行的时候会向 ZooKeeper 集群写入它的状态,当它故障的时候这些 RM 首先选举出另外一台 leader 变为 active 状态,然后从 ZooKeeper 集群加载 ResourceManager 的状态。在转移的过程中它不接收新的 Job,转移完成后才接收新 Job。
/ Spark on Yarn /
首先介绍 Spark 的资源管理架构。Spark 集群考虑到了未来对接一些更强大的资源管理系统(如 Yarn、Mesos 等)没有在资源管理的设计上对外封闭,所以Spark 架构设计时将资源管理抽象出了一层,通过这种抽象能够构建一种插件式的资源管理模块。
图 5,via http://shiyanjun.cn/archives/1545.html
如图 5 所示是 Spark 的资源管理架构图。Master 是 Spark 的 主控节点,在实际的生产环境中会有多个 Master,只有一个 Master 处于 active 状态。Worker 是 Spark 的工作节点,向 Master 汇报自身的资源、Executeor 执行状态的改变,并接受 Master 的命令启动 Executor 或 Driver。Driver 是应用程序的驱动程序,每个应用包括许多小任务,Driver 负责推动这些小任务的有序执行。Executor 是 Spark 的工作进程,由 Worker 监管,负责具体任务的执行。
以上就是 Spark 在资源管理上的抽象出来的架构,这个架构跟 Yarn 的架构十分相似,因此 Spark 很容易地构建于 Yarn 之上。在 Spark 和 Yarn 两边的角色对比中:Master 和 ResourceManager 对应,Worker 和 NodeManager 对应,Driver 和 App Master 对应,Executor 和 Container 对应。
根据 Spark 部署模式的不同资源管理架构也会有不同的形态。Spark 大致包括四种部署模式:
- Local 模式:部署在同一个进程上,只有 Driver 角色。接受任务后创建 Driver 负责应用的调度执行,不涉及 Master 和 Worker;
- Local-Cluster 模式:部署在同一个进程上,存在 Master 和 Worker 角色,它们作为独立线程存在于这个进程内;
- Standalone 模式:Spark 真正的集群模式,在这个模式下 Master 和 Worker 是独立的进程;
- 第三方部署模式:构建于 Yarn 或 Mesos 之上,由它们提供资源管理。
接着看看 Spark on Yarn 对 Job 的处理过程。客户端提交一个任务给 Yarn ResourceManager 后,App Manager 接受任务并找到一个 Container 创建App Master,此时 App Master 上运行的是 Spark Driver。之后 App Master 申请 Container 并启动,Spark Driver 在 Container 上启动 Spark Executor,并调度 Spark Task 在 Spark Executor 上运行,等到所有的任务执行完毕后,向 App Manager 取消注册并释放资源。
图 6,via https://www.iteblog.com/archives/1223.html
可以看出这个执行流程和 Yarn 对一个任务的处理过程几乎一致,不同的是在 Spark on Yarn 的 Job 处理过程中 App Master、Container 是交由 Spark 相对应的角色去处理的。
图 7,via https://www.iteblog.com/archives/1223.html
Spark on Yarn 还有另外一种运行模式:Spark on Yarn-Client。不同于上述的 Spark on Yarn-Cluster,Spark on Yarn-Client 的客户端在提交完任务之后不会将 Spark Driver 托管给 Yarn,而是在客户端运行。App Master 申请完 Container 之后同样也是由 Spark Driver 去启动 Spark Executor,执行任务。
那为什么使用 Yarn 作为 Spark 的资源管理呢?我们来对比 Spark 集群模式 Standalone 和 Spark on Yarn 在资源调度能力上的区别:Spark 的 Standalone 模式只支持 FIFO 调度器,单用户串行,默认所有节点的所有资源对应用都是可用的;而 Yarn 不止支持 FIFO 的资源调度,还提供了弹性和公平的资源分配方式。
Yarn 是通过将资源分配给 queue 来进行资源分配的,每个 queue 可以设置它的资源分配方式,接着展开介绍 Yarn 的三种资源分配方式。
FIFO Scheduler
如果没有配置策略的话,所有的任务都提交到一个 default 队列,根据它们的提交顺序执行。富裕资源就执行任务,若资源不富裕就等待前面的任务执行完毕后释放资源,这就是 FIFO Scheduler 先入先出的分配方式。
图 8,via https://blog.csdn.net/suifeng3051/article/details/49508261
如图 8 所示,在 Job1 提交时占用了所有的资源,不久后 Job2提交了,但是此时系统中已经没有资源可以分配给它了。加入 Job1 是一个大任务,那么 Job2 就只能等待一段很长的时间才能获得执行的资源。所以先入先出的分配方式存在一个问题就是大任务会占用很多资源,造成后面的小任务等待时间太长而饿死,因此一般不使用这个默认配置。
Capacity Scheduler
Capacity Scheduler 是一种多租户、弹性的分配方式。每个租户一个队列,每个队列可以配置能使用的资源上限与下限(譬如 50%,达到这个上限后即使其他的资源空置着,也不可使用),通过配置可以令队列至少有资源下限配置的资源可使用。
图 9,via https://blog.csdn.net/suifeng3051/article/details/49508261
图 9 中队列 A 和队列 B 分配了相互独立的资源。Job1 提交给队列 A 执行,它只能使用队列 A 的资源。接着 Job2 提交给了队列B 就不必等待 Job1 释放资源了。这样就可以将大任务和小任务分配在两个队列中,这两个队列的资源相互独立,就不会造成小任务饿死的情况了。
Fair Scheduler
Fair Scheduler 是一种公平的分配方式,所谓的公平就是集群会尽可能地按配置的比例分配资源给队列。
图 10,via https://blog.csdn.net/suifeng3051/article/details/49508261
图 10 中 Job1 提交给队列 A,它占用了集群的所有资源。接着 Job2 提交给了队列 B,这时 Job1 就需要释放它的一半的资源给队列 A 中的 Job2 使用。接着 Job3 也提交给了队列 B,这个时候 Job2 如果还未执行完毕的话也必须释放一半的资源给 Job3。这就是公平的分配方式,在队列范围内所有任务享用到的资源都是均分的。
/ the Future of Spark /
Mesos 的资源调度和 Yarn 类似,但是它提供了粗粒度和细粒度的两种模式。所谓的粗粒度和细粒度的差别在于:Executor 申请的资源是在执行前申请,还是在执行过程中按需申请。集群资源紧张时可能有一个 Executor 申请的资源在当时处于闲置状态,如果处于粗粒度模式下,这些资源在当时就浪费了。但是在细粒度模式下,Executor 执行时所需的资源是按照它的需求分配的,这样就不存在资源闲置的情况了。
因为 Mesos 的 Executor 是可以动态调整,而 Yarn 使用的 Container 是不可以动态调整的,所以目前 Yarn 是不支持细粒度的调度模式的,但 Yarn 已经有计划支持细粒度的资源管理方式。
除此之外在 Hadoop3.1.0 中 Yarn 提供了对 gpu 资源的支持,目前只支持 Nvidia gpu。期待 Spark 在其他方面的更多探索,下一篇我们将具体介绍 RDD,欢迎持续关注。