钟最龙 分布式实验室
Google最近公布了他们在系统基础设施方面的王冠宝石之一:Borg,他们的集群调度器。这促使我重新阅读讨论相同主题的Mesos和Omega的论文。我想做一个这几种系统的对比应该会有意思。Mesos因为开创性的两层调度(two-level scheduling)理念而受到美誉,Omega在这个基础上用类似数据库的技术做了改进,Borg可以看作是对所有这些思想的巅峰之作。
集群调度器可谓是历史久远,它出现在大数据的概念之前。在高性能计算(HPC)的世界中,关于如何调度上千个核心的的CPU已经有了非常多的资料,但相比于数据中心调度,核心调度的问题就简单多了。而Mesos/Borg等系统要解决的正式数据中心调度。接下来,让我们通过几个纬度对比下它们。
超级计算机可以将存储和计算分离,并用和内存速度差不多的近乎全等分带宽的网络将它们连接起来。这意味着你的任务可以放在集群的任何地方,而不用担心具体位置,因为所有的计算节点都可以快速访问到数据。有一些特别优化过的应用针对网络拓扑进行了优化,但是这些情况很少见。
数据中心调度器需要关心具体位置,实际上这是GFS和MapReduce共同设计的所有关键所在。21世纪初的时候,相比于硬盘带宽,网络带宽更加昂贵。所以为了降低成本,设计上会把数据存储和计算任务放到同一个节点上。这是一个主要的调度限制,尽管之前在你能将任务放到任何地方,如今你需要将它放在三个副本的某一副本中。
超级计算机通常由同类节点组成,例如他们都有一样的硬件配置。这是因为超级算计通常是一次性采购的:一个实验室获得了数百万的经费用于更新硬件。一些HPC的应用是根据某超级计算机中特定的CPU模型来优化的。新的技术如GPU或者协处理器,会当做一个新的集群推出。
在大数据的领域,集群主要受到存储的限制。因而运维会不断的添加新机架来为集群扩容。这意味着对于节点有着不同的CPU、内存能力、硬盘数量等现象很常见。同时也会添置一些特别如SSD、GPU或者叠瓦式硬盘(shingled drive)。一个单独的数据中心需要大范围的应用,而这一切又会强加额外的调度约束。
当在超级计算机上运行应用时,你需要指定需要的节点数,要提交作业的队列,以及作业的运行时间。队列会对你能请求多少资源和你的任务可以运行多久做不同限制。队列也有一个基于优先级或预留的系统,来决定排序。
因为这个任务的时长都知道了,这是一个十分简单的装箱的问题。如果队列很长(通常是这样),并且有相当多的小任务来填充大的任务留下的空间,你可以获得相当高的利用率。我喜欢将这个描述用2D的方式可视化呈现,时间是X轴,资源使用作为Y轴。
如前文所述,数据中心调度是一个普遍的问题。资源请求的形式可以各种各样,并且可以有更多的维度。任务也没有一个设定好的时间范围,因此来预先计划队列很困难。我们有一个更加复杂的调度算法,然后调度器的性能变得至关重要。
利用率一般而言是会变得越来越差。但是相比HPC工作量的一个优点是,MapReduce和类似的可以增量调度(incrementally scheduled)而不是成组调度(gang scheduled)。在HPC中,我们等到你请求的N个结点都到位了,然后一次性运行你的任务。
MapReduce可以在多次波动中运行它的任务,这意味着它仍然可以有效地利用剩余的资源。单一的MR作业也可以基于集群的需求起伏,避免了抢占或资源预留,并且还有助于实现多用户之间的公平性。
Mesos早于Yarn出现,设计的时候考虑到了最初MapReduce存在的问题。在那时候,Hadoop集群只能运行单一的应用:MapReduce。这就很难运行不符合一个map阶段跟着一个reduce的阶段的应用。这里最大的一个例子是Spark。
先前,你不得不为Spark安装一套全新的worker和master,用来和你的MapReduce的worker和master一起运行。从资源利用的角度来讲,完全不理想,因为他们通常是静态分区的。
Mesos可以为所有集群应用提供一个通用的调度器API来解决了这个问题。MapReduce和Spark变成了不同简单应用,使用的是同一个底层的资源分享框架。最简单的方式是写一个中心化的调度器,但是这有一些缺点:
API的复杂度。我们需要一个API,其得是所有已知的框架调度器API的超集。这是本身就很困难。如何表达资源的请求,同样变得十分复杂。
性能。数万的节点和数百万的任务数目不小。特别当调度问题复杂的时候。
代码的敏捷性。新的调度器和新的框架不断的出现,这会带来新的需求。
相反的,Mesos引入了新的两级调度(two-level scheduling)的概念。Mesos将每个应用程序的调度工作委派给应用程序本身,同时,Mesos仍然负责应用和执行整体公平性之间的资源分配。所以Mesos非常的轻量,只有10K行代码。
两级调度通过一个比较文艺的API叫做resource offer(资源邀约),Mesos会周期地为应用程序调度器提供一些资源。这在最初听起来很落后(请求从master发到应用?)但是实际上没有那么奇怪。在MR1中,TaskTracker(任务追踪器) worker了解节点的运行情况。
当一个TT的心跳说一个任务完成,JobTracker(工作追踪器)然后选择该TaskTracker上的其他任务。调度的决策是通过该worker中的一个资源邀约来触发的。在Mesos中,资源邀约由Mesos的master而不是slave发出,因为Mesos管理着集群。并没有太大的不同。
“资源邀约”就像一个对一些资源有时限的租约。Mesos根据如优先级和公平分享等策略向应用提供资源。然后,应用会计算如何使用这些资源,并告诉Mesos发放的资源中哪些是它需要的。
这给了应用提供很大的灵活性,因为其可以选择暂时运行一部分的任务,然后等待稍后更大的资源分配(成组分配),或者将任务进行不同的体积变更来适应分派的资源。因为资源是有时间限定的,这也促使应用尽快的进行调度。
一些担忧和它们的解决方案:
长时间的任务会独占着资源。Mesos能让你保留一些资源,来运行一些短线任务。然后在超过时间限制后杀掉。这也鼓励使用短线任务,其对公平有利。
性能隔离。使用Linux的容器(cgroups)。
大型任务的饿死。很难得到一个节点的唯一访问权,因为一些具有较小任务的其他应用程序会蚕食它。可以通过设定请求资源的最小规模来修复这一问题。
尚未解决的:
成组调度。我认为在不知道任务的长度,或者进行抢占的情况下是不可能达到高利用率的。不断你囤积资源在低利用率的情况下有用,但是可能会导致死锁
跨应用的抢占也并非易事。资源邀约API没有办法说,这里是一些低优先级的任务,如果你们有需要我可以停止他们。Mesos依靠任务的短的特性来获得公平。
Omega可以说是Mesos的后继者,并且实际上有一个共同的作者。因为该论文对于其评估使用的是模拟的结果,我怀疑其从来没有在Google进入过生产,并且这种理念被带入了下一代的Borg。重写API很可能太改动太大,即使对于Google来说。
Omega将资源邀约更进一步。在Mesos中,资源邀约是悲观的(pessimistic)或者是独占的(exclusive)。如果资源已发放给了一个应用,同样的资源不会发放给另外一个应用的,除非该发放超时。在Omega中,资源邀约是乐观的(optimistic),每一个应用都发放了所有的可用的资源,冲突是在提交的时候被解决的。
Omega的资源管理器,本质上是一个保存着每一个节点的状态关系数据库,并且用不同的乐观并发控制来解决冲突。这样的好处是其大大的提高了调度器的性能(完全的并行,full parallelism)和更好的利用率。
不足的的地方是,应用处于一种可以随时独占天下的状态,因为它们允许来任意快的吞食资源,甚至抢占在其他用户之前。这对于Google来说是可行的,因为他们使用一个基于优先级的系统,并且可以对这他们内部的用户吼。
他们的任务量大概分为两种优先级类:高优先级的服务类job(HBase,web服务器,长期存活的服务)和低优先级的批量式job(MapReduce等类似的)。应用允许抢占低优先级的job,并且可以受信任的呆在他们合作式强制来的范围内如提交的job数量,分配的资源大小等等。我认为分配的资源量。我想Yahoo对于能去对着用户吼有不同的说法(当然这并不能伸缩),但是这Google它不知怎样就可以。
论文大多数讨论的是这种乐观的分配策略是如何和冲突一起工作的,因为这总是一个问题,这有一个高层次的观点:
服务型工作更大型,并且有更激进的安放位置的需求,来实现容错(分布在不同的机架上)。
Omega可以伸缩到数十但不能伸缩到数百调度器,因为分发完整的集群状态的开销。
几秒的调度时间是很平常的。他们也比较数十秒和数白秒的调度,这是两层调度真正起效的地方。不确定这有多么的普遍,可能只对于服务类的工作才这样?
正常集群的利用率通常在60%。
冲突足够的少见,以至于OCC在实际中可行。在调度器崩溃之前,能达到6倍于他们正常的批量工作量。 增量调度是非常重要的。成组调度因为增长的冲突的实现起来十分的昂贵。显然,绝大部分的应用可以低的在增量能很好的运行,只需要一些部分的分配达到他们总体想要的量即可。
即使对于复杂的调度器来说(每作业数十倍的的额外开销),Omega仍能在合理的等待时间下调度混合的工作量。
在Omege中实验一个新的MapReduce调度器在经验上来说是十分容易的。
在一些情况下,乐观的并发控制会因为高冲突频率和因为重试的重复工作而崩溃。似乎他们在实际中不会遇到这个情况,但是我不知道是否在遇到奇怪形状的任务的时候是否会有最坏的场景。这受服务型和批量式的作业的影响么?这个问题是否在实际中是否会进行调优?
缺少全局的策略真的可以接受么?公平,抢占等等。
对于不同的作业的调度时间不同的时间是怎样的?是否有人已经写出十分复杂的调度器了?
Borg
这是一个生产环境的经验下的论文。它有与Omega一样的工作量,因为通出自于Google,因而他们很基本点是一样的。
任何东西都运行在Borg之中,包含存储系统如CFS和BigTable。
中等类型的集群大小有10k左右的节点,尽管有的要大的多。
节点可以是十分的异构。
使用了Linux的进程隔离(本质上来说是容器),因为Borg出现在现在的虚拟机基础设施之前。效率和启动时间当时十分重要。
所有的作业都是静态的链接的可执行文件。
有非常复杂,十分丰富的资源定义语言可用。
可以滚动升级运行的作业,这意味着配置和执行文件。这有时需要任务重启,因而容错是很重要的。
支持在最终被SIGKILL杀死之前,通过SIGTERM优雅的退出。也可以温柔的杀死,但不能依靠来保证正确性。
资源分配是和进程的存活分开的。一个alloc可以用组织任务或者来在任务间来保持资源。
一个alloc集(alloc set)是一个组分配在不同的机器上的alloc。不同的作业可以在同一个alloc里面运行。
这其实是一个常见的模式。多进程对于分离担忧和开发是有用的。
两类优先级的:高和低,分别用于服务类和批量类的。
较高优先级的作业能抢占较低优先级的作业。
高优先级的任务能互相抢占(防止连串的活锁场景)。
配额用于准入控制。用户需要付更高的费用以获得更高优先级的配额。
同时也提供了一个空闲的运行在最低优先级层,来鼓励高利用率和回填式的工作。
这是一个简单易于理解的系统!
两个调度的阶段:找到可用的节点,然后给这些节点打分用于最后的安置。
可用性很大程度由任务的约束条件来决定
打分最主要是根据系统的属性来决定的,像最佳匹配(best-fit)与最差匹配(worst-fit)的对比,作业混合(job mix),失效域(failure domains),局部性(locality)等等。
一旦最终节点被选择,Borg如有必要会进行抢占。
因为需要局部化依赖性( localizing dependencies),一般的调度时间在25秒左右。下载执行文件是占了80%的时间。这种本地化很重要。Torrent和树协议用来分发执行文件。
中心化并没有成为一个不可能的性能瓶颈。
调度频率在每分钟数万的节点,上万的任务。
通常Borgmaster使用10-14核和50GB左右的内存。
架构已经逐渐越来越多进程,参考Omega和两层调度。
Borgmaster虽然为单主,但是一些责任是仍然是共享的:来自worker的状态的更新,只读的RPC。
一些显而易见的优化:缓存机器的评分,没任务类型一次的计算可用性,在做调度的决策的时候不要尝试获得全局的最优性。
他们的主要指标是cell compaction(细胞压缩),或者叫能最满足一组任务所需要最小的集群。本质上即盒包装(box packing)。
从这些获得了的大的提升:不隔离任务量或用户,有大的共享的集群,细粒度的资源请求。 在一个每Borglet的基础上乐观的过量使用(Optimistic overcommit )。Borglet能做资源评估,并且回填非生产(non-prod)的工作。如果这个评估不正确,杀死非生产的工作。内存是非弹性化的资源。
分享不会大的影响CPI(CPU干扰,CPU interface),但是我比怀疑对于存储这一块的影响。
这里列举的问题在Kubernetes里面已经修复了,他们公开且开源的容器调度器里面。
坏处
能调度多作业的工作流而不是单作业会很好,有利于跟踪和管理。这也需要更灵活的方式来指带工作流的组件。这通过添加任意的键值对到每一个任务,并且让用户可以方便用户查询得到解决。
每一个机器一个IP。这带一个机器上端口的冲突,和复杂的绑定和服务发现。这能通过Linux的命名空间,IPv6,SDN得到解决。
复杂的定义语言。太多的疙瘩需要解开,这让一个普通用户很难上手。需要一些在自动决定资源需求方面的工作。
好处
Allocs 真是好极了。允许助手服务能方便的跟主要task放在一起。
内置的负载均衡和命名功能十分的有用。
指标,调试和Web界面,对于用于用户解决自己的问题十分的有用。
中心化向上扩展的很好,但是需要分散到多个进程里面去。Kubernetes从头做这个,意味在不同的调度器组件之间一个干净的API。
貌似YARN借鉴了Mesos和Omega的设计,才使得伸缩规模达到10K的节点。相比于Mesos和Omega,YARN仍然是一个中心化的调度器。
为了获得高利用率的同时,又不牺牲SLO,隔离就非常重要。这可以在应用层面来实现,同时,应用也需要可以容忍时延。想一想BigTable中tail-at-scale的请求复制。最终,这归结于硬件的开销与软件开销的对比。运行在低利用率可以回避这个问题。或者你可以通过OS的隔离机制,资源评估和调优你的工作量和调度器来解决这个问题。
在Google的那样的规模,有足够的硬件招聘一批内核的开发者相当合理。幸运的,他们已经会我们做了这件事情。幸运的是他们已经为我们做了这些事情。
我也怀疑如果Google工作量的设想应用到更普通的场景,优先级的分类,资源保留和抢占对于Google来说奏效,但是我们的客户几乎都使用公平分享的调度器。Yahoo使用的容量调度器。Twitter使用公平的调度器。我还没有提说过任何需要需要优先级+资源保留的调度器。
最后,很少的我们客户运行大的分享的集群如在Google看到的情况。我们有客户有数千的节点。但这分散到了数白节点上的不同的pod里面。对于不同的客户和应用,有分开的集群来说也很常见。集群也通常在硬件上是同构的。但我想这会改变,并且会很快。