概述
不管是计算任务还是数据存储都会涉及资源分配,资源包括但不限于硬件资源如CPU、内存、硬盘、网口。在单机环境中,资源管理相对简单;分布式环境中,资源分布相对分散,如何协调资源应对计算任务和数据存储就是亟待解决的问题。
资源管理和调度是将计算任务分配到资源的过程,为了处理并发的计算任务,系统会通过集群的方式组织资源。集群中的资源可以按照服务器或者虚拟机的方式划分。
注:本文是《分布式架构原理与实践》的读书笔记。
静态资源分配和动态资源分配
也可翻译为静态资源调度和动态资源调度。
静态资源分配
简单来说,静态资源分配就是提前知道系统面临的几种计算任务,且预设各个资源节点是稳定且可靠的,此时直接将各计算任务分配给对应的资源执行即可。
显而易见,静态资源分配存在如下问题:
- 如果某种类型的计算任务比较简单,任务很快执行成功,而另一种类型的任务执行耗时久,比如MR任务执行成功后,Spark任务还在排队中。则存在资源节点的闲置(浪费),没有充分使用预分配的系统资源
- 在系统进行资源扩容不方便,如果用户提交过多且比较紧急的Spark任务,能不能增加资源节点
动态资源分配
鉴于上面静态资源分配这种调度方式的弊端,可考虑引入一个调度器。当某种类型的计算任务执行完毕后,资源调度器会释放相应的资源,从而让其他计算任务有机会获取资源。如果有部分资源节点不可用,也不会影响整个集群的正常使用,所有资源节点都会在资源调度器的安排下完成计算任务。即便是对整个集群进行扩容,也只需把注意力放到资源节点的扩充上即可,任务与资源的动态匹配过程由资源调度器完成,实现计算任务和资源的解耦。
动态资源调度的优势:
- 动态资源分配会根据计算任务实时分配资源,通常不会出现资源闲置的情况,只要没有达到资源的使用上限,是不会出现任务匹配不到资源的情况的。总体来说,资源利用率较高,硬件成本较低,有良好的扩展性
- 需要收集资源的整体信息,形成资源池以便调配,因此能增加数据共享功能,所有计算任务的请求都可以共享资源
- 动态资源分配方式同时支持如Spark、Storm、MapReduce等计算框架。让计算框架和资源利用得以完全解耦,使资源管理和调度平台实现平滑切换
资源调度
指的是根据调度策略对资源和计算任务做匹配。从参与者的角度讲,涉及资源、资源调度器和计算任务三部分。三者的关系,根据调度策略对计算任务和资源进行配对。常用的调度策略,如FIFO策略、公平策略、能力策略、延迟策略。
资源调度器中包含工作队列、调度策略、资源池和资源收集器。工作队列既是用来存放计算任务的容器,也是资源调度器组织和管理计算任务的一种形式。调度策略包含所需的任务调度策略,也就是对资源和计算任务进行匹配的算法。资源池是对收集起来的硬件资源进行存储和管理的地方。资源收集器,就是对资源节点上报的资源进行收集和汇总。
资源调度的整个过程解析:
- 硬件资源(CPU、内存、硬盘、网口)分布在不同的网络节点上,为了让资源调度器能够更好地组织和管理它们,在每个资源节点上都安装节点管理器。节点管理器主要负责不断地向资源收集器汇报资源节点上的资源情况。管理资源的时候,需要按照规则对资源进行分割,这里暂且将这些分割后的资源称为容器。设置容器的目的是将资源隔离,每个容器中都会运行指定的计算任务,隔离的做法使得计算任务的执行互不影响。节点管理器负责管理这些容器,并且将容器的运行情况和资源使用情况汇报给资源收集器。
- 资源收集器接收到节点管理器上报的资源使用情况以后,将这些资源放入资源池中实施管理。这里的资源池是一个逻辑概念,其实际存放的是目前可用的资源信息。这些信息会提供给调度策略使用,当有计算任务到来时,通过调度策略在资源池中选择资源分配给它。
- 资源池的信息也会上报到调度策略中。调度策略中维护着FIFO、公平调度、能力调度、延迟调度等分配算法。
- 调度策略会不断监控工作队列中的计算任务,并依次处理这些任务——使用调度策略对计算任务与资源做匹配,让计算任务在具体的容器中执行。当计算任务执行完毕以后,资源调度器会回收资源,并通过节点管理器将空闲资源上报给资源收集器,使其重新回到资源池中等待被分配。
- 当有计算任务请求资源调度器的时候,会将这些任务放到工作队列中以便管理。资源调度器在这里起到承上启下的作用。承上是对接计算任务框架并对其进行组织;启下是获取资源信息并对其进行抽象;最后,对计算任务和资源按照策略进行匹配。同时这也是一个动态资源分配的过程,这种方式可用支持不同的计算框架。真正做到计算任务和资源的解耦。
架构
在Malte Schwarzkopf的论文Omega: flexible, scalable schedulers for large compute clusters中,提到集群调度器的架构演化经历中央式调度器、两级调度器和共享状态调度器:
- 中心化调度:也叫单体调度,由一个网络节点参与资源的管理和调度
- 两级调度:在单体调度的基础上将资源的管理和调度从一层分成两层,分别是资源管理层和任务分配层
- 共享状态调度:通过共享集群状态、共享资源状态和共享任务状态完成调度工作
中央式调度器
Monolithic Scheduler,也叫单体式调度器,指在集群中只有一个节点能够运行调度程序,要保证该节点对集群中的其他节点都有访问权限,该节点可获取其他节点的状态信息和资源,并且管理这些节点。中央式调度器同时也是用户请求执行任务的入口。当用户发起计算任务时,调度器就会对请求任务与自己管理的资源进行匹配,然后将计算任务分配给指定的资源节点,完成调度工作。中央式调度器需要维护资源列表和任务列表,以便对资源和任务进行约束并执行全局调度策略。
采用中央式调度器的集群管理系统还不少,如Google Borg。与分布式资源调度的架构相似,中央式调度器一边接收计算任务的申请,一边对管理资源,同时通过任务调度策略将两者匹配在一起。
调度过程分为以下三个步骤:
- 调度器的资源状态管理模块通过集群中节点上的资源管理器收集节点的资源信息。位于节点1~3上的资源管理器会将节点本地的资源信息汇集给调度器
- 调度器的资源状态管理模块会接收用户发起的计算任务,并通过对集群中节点的资源掌握情况,将任务交由任务调度策略模块处理
- 任务调度策略模块根据具体的调度策略将任务分配给集群中的节点,使其运行
核心思想是由同一服务器管理和调度集群中所有的节点资源以及计算任务。好处是实现起来容易,缺点是负责管理和调度的服务器会成为性能瓶颈,限制调度规模和服务类型。
Hadoop MapReduce 1.0,就是通过中央式调度的方式对任务和资源做匹配,有不少局限性,如扩展性差、可靠性差、资源利用率低以及对无法支持多种计算框架等。
两级调度器
Two-Level Scheduler,也可译为两层调度,为了解决中央式调度器的单点性能瓶颈问题,可考虑将资源管理和任务调度分开,即一层调度器负责资源管理,一层调度器负责任务与资源的匹配。两级调度架构的经典代表,如Hadoop 2.0 YARN(Yet Another Resource Negotiator)、Mesos。
将原来中心化的单个调度器分成两级,目的是更好地应对复杂的计算框架和高并发的计算任务。一级调度器的任务原来是管理资源和任务的状态,而面向不同的计算架构扩展出多个二级调度器后,一级调度器的主要任务就是匹配任务与资源,也就是执行调度策略。二级任务调度器又称为框架调度器,是面向不同类型的计算框架对一级调度器的扩展。假设调度架构需要处理Spark、MapReduce、Storm三类不同的计算任务,就可以在二级任务调度器中对调度框架进行扩展,既起到与调度策略解耦的效果,又可以应对高并发的计算任务。
调度步骤:
- 一级调度器的资源任务状态管理模块通过集群中节点上的资源管理器收集各节点的资源信息,位于节点1~3中的资源管理器会将节点本地的资源信息汇总到一级调度器上。和中央式调度器是一样,管理资源信息的工作依旧是由一级调度器完成
- 一级调度器的资源任务状态管理模块会接收用户发起的计算任务,通过对集群中节点的资源掌握情况,将计算任务交由二级调度器对应的任务调度策略器处理
- 二级调度器接收到任务调度请求后,根据计算框架的调度策略对资源和任务进行匹配。对不同类型的计算任务进行分类。另外,二级调度器支持水平扩展,可提高节点的处理能力
- 二级调度器在处理完任务调度以后,会将结果返回给一级调度器,然后根据调度策略的结果将任务分配到对应的服务器节点上运行。一级调度器会不断地监控资源和任务运行情况,保证任务的顺利运行
两级调度器解决调度器扩展和处理大数据量任务请求的问题,但还是无法看到系统的所有资源。调度器没有全局视角,无法知道作业可以被分配到哪个服务器节点上运行,仅能感知资源管理器收集并提供的资源,以及资源管理器分配给应用程序/作业的部分资源。总结为以下两点:
- 无法进行全局优化:考虑集群资源紧张时,如果有一个任务运行失败,任务应该如何分配呢?即如何选择执行节点?如果调度器能够获取整个系统的资源情况,这样可以优化资源调度。
- 悲观锁导致并发量受限:在匹配任务与资源的时候,为了避免资源抢占冲突,即避免不同的作业抢占同一个资源,通常会对资源做加锁操作。两级调度器采用的是悲观锁并发调度,也就是事前预防资源抢占的情况。具体地,在任务运行之前首先检查是否存在资源冲突的情况,如果不存在冲突就继续运行,否则等待或进行回滚操作。在调度过程中,会将所有资源依次推送给每个计算框架,让这些框架依次匹配资源,从而保证不会出现多个计算框架使用同一块资源的情况。这种做法虽然避免资源抢占冲突,但同时也减少系统任务的处理并发量。
共享状态调度器
Shared State Scheduler,针对两层调度系统存在的全局优化和并发调度受限问题,提供两点措施:
- 让集群全局资源对每个调度器均可见,把集群资源信息转化成持久化的共享数据(状态);
- 采取多版本并发访问控制(MVCC)的方式对共享数据进行访问,也就是常说的乐观锁。乐观锁并发调度,强调事后检测,会在任务提交的时候检查资源是否存在冲突,如果不存在冲突就继续运行,否则进行回滚操作或者重新运行。也就是说,它是在执行匹配调度算法之后进行冲突检测,优点是能够处理更高的并发量。
乐观并发调度和悲观并发调度的对比:
- 乐观并发调度:强调事后检测,在事务提交时检查是否避免冲突,若避免,则提交,否则回滚并自动重新执行。
- 悲观并发调度:强调事前预防,在事务执行时检查是否会存在冲突,不存在,则继续执行,否则等待或者回滚。
共享状态调度的理念最早是Google针对两层调度器的不足而提出的,典型代表有Google Omega、微软的Apollo,Hashicorp Nomad容器调度器。
Omega使用共享状态调度的思想,在一个统一模块中完成以下功能:对整个集群中的所有资源分组;限制每类应用程序的资源使用量;限制每个用户的资源使用量等。只是将优先级限制放到共享数据的验证代码中,当多个作业同时申请同一份资源时,优先级最高的作业将获得该资源,其他资源限制全部下放给各个调度器执行。
Omega架构中没有中心资源分配器,所有资源分配决策都由应用的调度器自己完成。与Borg架构类似,Omega中也有一个单元(Cell)的概念,每个单元分别管理集群中的一部分服务器节点,一个集群由多个单元组成。单元中资源的状态由Cell State记录,同时Omega会在State Storage里维护Cell State的主复制,每个调度器中都会维护一个Cell State的备份信息,用来和State Storage保持同步,从而保证每个调度器都能够获得全局的资源信息。
集群里有多个调度器,主要负责作业和资源的调度工作,定期与State Storage同步Cell State的主复制,通过不断地同步可以获得集群中所有节点的资源情况。State Storage会不断地从集群中搜集Cell State信息,用作调度器同步。当调度器确定作业与资源的分配方案后,会通过原子的方式更新共享的Cell State,也就是通过乐观锁的方式将分配方案提交给Cell State。之后,调度器会重新同步本地的Cell State到State Storage的Cell State中,此时其他调度器可以通过同步State Storage中的Cell State,得知全局资源的使用情况。
Omega使用事务管理状态的设计思想,将集群中资源的使用和任务的调度类似基于数据库的一条条事务去管理。调度器对Job的调度是具有原子性的,一个Job中所有的Task都是一起调度的,即使部分Task调度失败,调度器再次调度时必须调整整个Job。Omega的调度流程如下图所示:
对比
关于单体调度、两层调度和共享状态调度的详细比较
对比项\调度 | 中央式调度 | 两级调度 | 共享状态调度 |
---|---|---|---|
调度架构 | 集中式结构:一个中央树调度器 | 形结构:一个中央调度器,多个第二层调度器 | 分布式结构:多个对等调度器 |
调度形式 | 单点集中调度 | Resource Offer | Transaction |
调度单位 | Task | Task | Task |
任务调度的并发性 | 无并发 | 悲观并发调度 | 乐观并发调度 |
是否是全局最优调度 | 是 | 否 | 是 |
系统并发度 | 低 | 中 | 高 |
系统可扩展性 | 低 | 中 | 高 |
调度效率 | 低 | 中 | 高 |
适用场景 | 小规模集群,适用于业务类型比较单一的场景 | 中等规模集群,适用于同时具有多种业务类型的场景 | 大规模集群,适用于同时具有多种业务类型的场景 |
典型应用 | Borg | Mesos、YARN | Omega |
参考
- 分布式架构原理与实践
- 调度框架:共享状态调度