本篇博客是大规模集群任务调度系列的第一篇。资源调度在亚马逊、谷歌、Facebook、微软或雅虎都得到了很好的实施,其他地方的需求也在增长。调度是一个重要的话题,因为它直接关系到运行集群的投资:糟糕的调度器将导致利用率低下,昂贵的硬件资源将被浪费。并且它本身无法实现高利用率,因为与资源利用率冲突的负载必须仔细配置和适当调度。架构演进本博客讨论调度架构在最近几年如何演化以及为何演化。图1演示了不同的方法:灰色方块对应一个设备,不同颜色的圆圈对应一个任务,带有“S”的方块代表一个调度器。箭头代表调度器的调度决策;三种颜色代表不同的工作负载(例如,网站服务、批量分析和机器学习)。图1:不同的调度架构,灰色框代表集群设备,圆圈代表任务,Si代表调度器。(a)单体调度器(b)二级调度(c)共享状态调度(d)分布式调度(e)混合调度与Hadoop调度器和Kubernetes调度器完全不同,是一个单一的调度器。单体式调度单个调度进程运行在一台物理机上(如HadoopV1中的JobTacker,Kubernetes中的kube-scheduler),将任务分配给集群中的其他物理机。所有工作负载都受制于一个调度器,所有任务都通过这个调度逻辑运行(见图1a)。这种架构是最简单也是唯一的格式。在此基础上,开发了许多负载调度器,例如Paragon和Quasar调度器,它们使用机器学习方法来避免不同负载之间的资源竞争。今天的集群运行不同类型的应用程序(对应早期的MapReduce作业场景),然而,很难使用单一的调度器来处理如此复杂的异构工作负载,原因有几个:调度器必须处理长期的这是一个合理的要求运行服务作业和批量分析作业。因为不同的应用有不同的需求,调度器增加了更多的功能,增加了业务逻辑和部署方式。调度程序处理任务的顺序成为一个问题:如果调度程序设计不当,排队效应(例如队头阻塞)和回滚可能成为一个问题。总而言之,这听起来像是工程师的噩梦,调度程序维护人员面临的无休止的功能请求证实了这一点。二级调度二级调度通过将资源调度和任务调度分离来解决这个问题,使得任务调度逻辑不仅可以根据不同的应用需求进行裁剪,还保留了集群间共享资源的可能性。虽然侧重点不同,但Mesos和YARN的集群管理都是采用这样的方式:在Mesos中,资源被提供(offer)给应用层进行调度,而YARN则让应用层去调度请求(request)资源(然后接受分配资源)。图1b显示了这个概念。作业负责调度(S0-S2)并与资源管理器交互,资源管理器为每个作业分配动态资源。该解决方案为客户提供了灵活安排工作策略的可能性。但是,通过两级调度解决问题也存在一个问题:应用层调度隐藏了资源的全局调度,即全局可选的资源配置再也看不到了。而是只能看到资源管理器主动提供(offer,对应Mesos)或者请求/分配(request/allocate,对应YARN)给应用的资源。这带来了一些问题:实现可重入优先级变得非常困难(即高优先级会移除低优先级任务)。在offer-based模式下,运行任务占用的资源对于上层调度器是不可见的;在基于请求的模式下,底层资源管理器必须了解重入策略(与应用程序相关)。调度器无法干预正在运行的事务,这可能会降低资源使用效率(例如,“饥饿的邻居”占用IO带宽),因为调度器看不到它们。应用相关的调度器更关注底层资源使用的不同情况,但它们选择资源的唯一途径是资源管理器提供的Offer/request接口,这很容易变得非常复杂。共享状态架构共享状态架构通过采用半分布式模式解决了这个问题。在这种架构下,集群状态的多个副本将由应用程序级调度程序独立更新,如图1C所示。一旦本地有更新,调度程序就会发出一个并发事务来更新所有共享的集群状态。有时事务更新可能会失败,因为另一个调度程序发出了冲突的事务。共享状态架构最重要的例子是谷歌的Omega系统,以及微软的Apollo和Hashicorp的Nomad容器调度程序。在这些例子中,共享集群状态架构是通过一个模块来实现的,在Omega中是“cellstate”,在Apollo中是“resourcemonitor”,在Nomad中是“planqueue”。Apollo与其他两者的区别在于共享状态是只读的,调度事务直接提交给集群设备;设备本身会检查冲突来决定是接受还是拒绝更新,这样即使共享状态暂时不可用,Apollo也可以。继续执行。从逻辑上讲,共享状态设计不一定要将完整状态分发到其他地方。通过这种方式(有点像Apollo)每个物理设备维护自己的状态并向其他感兴趣的代理发送更新,例如调度程序、设备健康监控和资源监控系统。每个物理设备的本地状态成为全局共享状态的“通信片”(分片)。但是,共享状态架构也有一些缺点,必须作用于稳定的(过时的,陈旧的)信息(这与集中式调度器不同),在高竞争情况下可能导致调度器性能下降(尽管其他架构也有这种可能性)。全分布式架构看起来更分散:调度器之间没有协调,使用许多独立的调度器来响应不同的负载,如图1d所示。每个调度程序都在其自己的本地(部分或经常陈旧的)集群状态信息上运行。通常,作业可以提交给任何调度程序,调度程序可以发布要在任何集群节点上执行的作业。与二级调度器不同,每个调度器没有负责的分区。全局调度和资源划分服从统计显着性和随机分布。这有点像共享状态架构,但是没有中央控制。虽然去中心化(decentralizedrandomselection)的底层概念出现在1996年,但现代意义上的分布式调度应该从Sparrow论文开始。当时有讨论:细粒度任务有很多优势,Sparrow论文的关键假设是集群上的任务周期可以变得很短;接下来,作者假设大量任务意味着调度器必须支持高吞吐决策,而单个调度器无法支持如此高的决策量(假设每秒百万任务),Sparrow分散负载跨越许多调度程序。这种实现意义重大:理论上去中心化意味着更多的仲裁,但它非常适合某些类型的工作负载,我们将在本系列后面讨论。现在,有足够的理由证明,由于分布式调度是不协调的,相对于复杂的单体调度,两级调度或分布式状态时间调度更适合简单的逻辑。例如:分布式调度基于一个简单的“时隙(slot)”概念,将每个设备划分为n个标准时隙,同时运行n个并发任务,尽管这种简化忽略了任务资源需求不同的事实.在任务端(worker端),使用遵循简单服务规则的排队方式(如Sparrow中的FIFO),这样调度器的灵活性就受到限制,调度器只需要决定将任务入队到哪个设备上.因为没有中央控制,分布式调度器很难设置全局变量(例如,公平策略或严格的优先级优先等)。由于分布式调度旨在基于最少的知识做出快速决策,它无法支持或承担复杂的应用相关的调度策略,因此完全分布式调度很难避免任务之间的干扰。混合架构混合架构是为了解决全分布式架构的缺陷。是最近提出的一个解决方案(源于学术派),融合了singleorsharedstate的设计。这种方式,如Tarcil、Mercury、Hawk,一般有两种调度路径:一种是为部分负载(例如短期任务或低优先级批量负载)设计的分布式路径,另一种是集中式调度,它处理剩余的负载,如图1e所示。在混合架构中发挥作用的调度程序对于所描述的工作负载来说是独一无二的。事实上,据我所知,目前生产系统中还没有部署真正的混合架构。实际意义不同调度架构的相对价值。除了许多研究论文外,讨论也不限于学术界。从行业角度深入探讨Borg、Mesos和Omega的论文,可以参考AndrewWang的专业博客。然而,上面讨论的许多系统已经部署在大型企业生产系统中(例如,微软的Apollo、谷歌的Borg、苹果的Mesos),这反过来又启发了其他可用的开源项目。如今,许多集群系统运行容器化的工作负载,因此出现了一系列面向容器的“编排框架”(OrchestrationFramworks),类似于谷歌等所谓的“集群管理系统”。然而,很少有人讨论这些调度框架和设计原则,更多地关注面向用户的调度API(例如,这份ArmandGrillet报告比较了DockerSwarm、Mesos/Marathon和Kubernetes的默认调度程序)。然而,很多客户既不了解不同调度架构的区别,也不知道哪种调度架构更适合自己的应用。图2展示了开源编排框架的一部分架构和调度器支持的功能。在图表的底部,还包括了谷歌和微软的闭源系统以供比较。资源粒度栏显示调度器是将任务分配到固定大小的时隙,还是根据多维需求(如CPU、内存、磁盘IO、网络带宽等)分配资源。图2:常用开源编排框架的分类和功能对比,以及与闭源系统的对比。确定合适的调度架构的主要因素是您的集群是否正在运行异构(或混合)工作负载。例如,混合使用前端服务(例如,负载平衡Web服务和内存缓存)和批处理数据分析(例如,MapReduce或Spark)对系统利用率很有意义,但不同的应用程序需要不同的调度方法。在混合设置下,单一调度很可能导致任务分配不是最优的,因为基于应用需求,单一调度逻辑无法多样化,此时,两级或共享状态调度可能更合适。很多资源跑向用户服务负载一般满足容器的峰值需求,但实际上资源都是overallocated的。在这种情况下,减少资源过度分配给低优先级工作负载的机会是高效集群的关键。虽然Kubernetes有比较成熟的解决方案,但Mesos是目前唯一支持这种超分配策略的开源系统。这个功能未来应该会有更大的提升空间,因为根据谷歌borg集群来看,很多集群的利用率还不到60-70%。在以后的博客中,我们将讨论资源估算、过度分配和有效的设备利用。***,特殊分析和OLAP应用(例如Dremel或SparkSQL)非常适合全分布式调度。而全分布式调度(如Sparrow)内置了比较严格的函数设置,所以当工作负载同构(即所有任务同时运行)时,设置时间短(即长Time-to-run,比如MapReduce应用任务运行在YARN上),适用于任务吞吐量(churn)高(即必须在短时间内做出很多调度决策)的情况。我们将在下一篇博客中详细讨论这些情况,以及为什么全分布式调度(以及混合架构中的分布式模块)只对这种应用场景有效。现在,我们可以证明分布式调度比其他调度架构更简单,不支持其他资源维度,不支持超分配或重调度。总而言之,图2中的表格表明,相对于更先进但闭源的系统,开源框架仍有很大的改进空间。可以针对缺少的功能、使用不当、不可预测的任务性能、嘈杂的邻居降低效率以及微调调度程序以支持某些特定于客户的需求采取行动。然而,也有很多好消息:虽然今天许多集群仍然使用整体调度,但许多已经开始迁移到更灵活的架构。Kubernetes今天已经可以支持可插拔调度器(kube-schedulerpod可以被其他兼容API的调度pod替代),从1.2版本开始,更多的调度器将支持“扩展器”以提供定制的策略。DockerSwarm,据我了解,未来也会支持可插拔调度器。下一步,下一篇博客将讨论全分布式架构是否是可扩展集群调度的关键技术创新(反对声音说:没必要)。然后,我们将讨论资源适配策略(提高利用率),最后讨论我们的Firmament调度平台如何结合和共享状态架构和单一调度质量,以及全分布式调度器性能问题。
