今天是分布式专题的第12篇文章,我们继续来看集群资源管理系统。
上一篇的文章当中我们简单了解了一下什么是分布式集群资源管理,它的诞生背景和解决的问题是什么,以及它大概有哪些优点和不足。上一章的内容比较表面,没有过多深入原理,这一篇文章我们一起来看看集群管理系统的原理部分。
对一篇文章有所遗忘,或者是最新关注的同学可以点击下方的传送门,回顾一下上篇的内容。
浅谈分布式集群资源管理系统原理
局部优先的原则
在大数据应用的场景下有一个基本的设计原则:我们通常是将计算分配到存储数据的节点执行,而不是从节点拿到需要的数据再来进行计算。这背后的原因很容易想通,因为这样可以尽量减少节点之间的网络通信,减少了数据传输。要知道大数据场景下的数据的规模是非常大的,动辄TB,PB,少则也有几百几十GB,一旦需要网络传输数据带来的开销是非常可观的。
我们把这个原理总结一下,可以称为”局部优先“原则,也就是说执行任务的节点越局部越好,如果在一台物理机当中就太完美了,因为这样可以避免所有的数据传输。
我们可以简单地根据这个原则来衡量一个集群调度系统的能力,我们根据局部性的情况可以简单地列出三种级别。第一种是节点局部性,也就是所有的计算都发生在一个节点当中,这样可以避免所有的数据传输,这也是最佳情况。第二种要差一些叫做机架局部性,也就是说所有的计算虽然不能保证在一个节点当中执行,但是至少可以保证这些执行的机器在同一个机架当中。在机架当中的机器可以通过内部的方式传输数据,而不必借助外部网络,这样的传输也要快得多。最差的情况就是节点分散在不同的机架,没有办法做任何加速,这种称为全局局部性,会带来大量的开销。
我们都知道一个好的集群调度系统要把集群当中所有机器的性能”压榨“到极致,但实际上固定的计算消耗的计算资源基本上是稳定的。除了机器的利用率之外,另一个关键点就是这个。而且有时候网络IO带来的开销可能比机器低使用率更可怕。
资源分配细节
关于资源分配可能我们直观理解上很简单,当有机器空闲的时候我们就分配给它任务,然后执行完了我们把资源收回,但是在实际的运用当中还存在很多细节需要我们精心设计和思考,一旦稍微有点没想清楚可能就会造成严重的后果。
我们来看两个问题,第一个问题是当我们当下新来了一个任务,但是没有足够资源的时候应该怎么办?
我们很容易想到有两种策略,第一种策略就是什么也不做,等。等到有任务结束了,资源释放出来了之后,我们再执行当前这个任务。但如果我们当前的任务是一个非常紧急的任务呢?有可能现在正在执行的任务优先级并不高,但是占用的资源又多又长,难道我们还要一直等下去吗?
所以我们会想到还有第二者策略,就是抢。既然我们当下的资源优先级高,正在执行的任务优先级低。我们可以先从优先级低的任务当中抢一部分资源过来,先把当下重要的任务执行了再说。等优先级高的任务执行了之后再去执行优先级低的。但是很遗憾,这么做是有问题的。技术上的问题我们先不谈,等会再说,有没有想过这么搞的话,还会有谁愿意把任务的优先级调低呢?肯定大家执行的任务都是最高优先级。到后来会发现所谓的优先级高低设置全部变成了摆设,正在运行的优先级都一样,都是最高优先级。
关于上面的问题还有后续,我们先放一放,来看另外一个问题,这个问题是上面这个问题的衍生品。不同的任务需要的资源不同,并且需要资源的情况也不相同。比如有些任务资源多少都可以运行,比如spark或者是MapReduce,资源多就多用几个机器,资源少就少用几个,但是不管多少都可以跑,无非是执行的时间长短不同。但是有些任务就不是如此,比如机器学习的任务,可能一次性需要大量的资源,并且就要这么多,少了就跑不了。那么问题来了,当我们新来了一个任务,当下资源不足以满足它分配需要的时候,我们是先留着不分配给它,等资源足够了一次性分配呢,还是先分配一点,后面拿到一点资源再继续分配呢?
你看,看起来平淡无奇的分配策略其实当中还是有很多棘手的细节。事实也的确如此,这也是为什么我之前说目前的集群资源管理系统还远远没有成熟才刚刚起步的原因,因为对于以上的问题都没有特别好的解决方案。
饿死与死锁
还记得上面的两个问题么,如果这两个问题没有回答好,那么就会出现饿死与死锁的情况。
饿死是指任务一直得不到调度,比如由于设置的优先级不合理。只有你一个老实孩子给自己觉得一个不太重要的任务设置了一个正常的优先级,而其他的老司机纷纷给自己的任务设置了最高优先级。由于一直有高优先级的任务被提交进来,所以你的任务被一直延后,你以为很快就能得到结果,可能到下班你的任务还没开始执行。在这种情况下,你要么同流合污,也把自己的任务设置成最高优先级,要么就只能一直等下去,或者因为完成的工作不够多而面临绩效以及老板的压力。
于是,这就从了一个简单的任务调度的问题上升到了内心价值观的考验,这也是一个经典的劣币驱逐良币的过程。由于少部分人的不遵守规则,导致反倒是遵守规则的人受到惩罚,想想真是男默女泪[狗头]。
死锁的情况比较容易理解,如果学过操作系统的同学应该很熟悉,原理是一样的。打个比方,如果我们当下有AB两个任务,这两个任务都需要集群2/3的资源才可以执行。由于这两个任务是差不多时间点提交的,而系统采取了先来先分配的原则,给这两个任务都分配了1/2的资源。那么这就会导致死锁,因为这两个任务没有一个能执行,也就没有一个任务会释放资源,所以除非人工kill掉一个,否则会一直如此持续下去。
从目前的情况来看,好像没有一个完美的方法可以完全避免这两种情况出现。只能架构师根据自己集群调用的实际情况进行决策,并且还要加入一些人工干预的因素,比如在团队当中约法三章制定一些规范和条例等等。也就是说,某种程度上来说这已经不只是一个系统的问题了,而是一个系统和团队协调的复杂问题。
调度器
下面我们来看看常见的调度器的架构,常见的调度器有三种,第一种是集中式调度器,第二种是两级调度器,最后一种是状态共享调度器。
集中式调度器
我们先来看集中式调度器,它最直观也最简单:
它的设计逻辑就是集中,整个系统当中只有一个全局的中央调度器。所有的框架或者是计算任务都由中央调度器来实现。有点像是封建时候的宗族制度,整个大家族当中所有的大事小事全部由一个人来管理。显然这样做的弊病非常多,刚才提到的两个问题都需要人工干预,否则很难解决。
后来在此基础上进行了改进,在整个中央调度器当中又加上了分支逻辑。这种调度器被称为多路径调度器:
整体而言变化不大,只是多了一个条件判断。也就是说内部实现了针对不同种类的任务执行不同的调度和分配策略。比如如果是小的碎片任务,那么执行优先级管理,先来先得策略。如果是大的机器学习任务,只有拿到完整资源才会执行,防止死锁等等。
相比于单路径集中调度而言,多路径集中调度增加了一些灵活性,但是整体的拓展性还是远远不够,并且并发能力也比较差,资源的利用率不够高,如果规模大了,调度性能很容易成为瓶颈。但是它胜在架构简单,容易维护。
两级调度器
由于集中式调度器有许多的问题,也不够灵活,我们为了增加它的灵活性,在此基础上又增加了一层结构:
我们同样有一个中央调度器来坐镇指挥,只不过中央调度器并不会直接调度任务,而是会以一种比较粗粒度的策略将集群当中的资源分配给框架调度器。具体调度、执行任务的逻辑在框架调度器当中。相比于中央调度器,框架调度器执行的策略会更加细粒度一些。
另外,只有中央调度器可以看到整个集群当中所有资源的情况,框架调度器只能看到自己分配到的那一部分资源。我们比较熟悉的YARN,Mesos都是采取的这种架构。
有了框架调度器之后,我们可以在不同的框架调度器当中执行不同的策略。有助于提升整个集群的并发能力,以及资源利用率。所以总体而言,两级式调度器的性能要比集中式调度器好得多。
但是即使是这样,也不是完美的。因为中央调度器在调度的时候执行的是一种悲观并发的策略。简单解释就是在执行分配的过程当中,中央调度器会严格按照事先制定的顺序,并且会对资源加锁来防止不同框架申请资源时导致的冲突。既然用到了悲观锁,显然也会影响整体的性能。
状态共享调度器
状态共享调度器的架构和两级调度器非常接近,可以简单理解成了去掉了中央调度器的结果:
这个架构最早出现在Google公司的Omega调度系统当中,Omega调度系统是现在非常火热的Kubernetes的前身。它和两级式调度器的最大的不同就是没有中央调度器,所有的框架调度器可以直接看到整个集群当中的所有资源。在需要使用资源的时候,由框架调度器之间互相竞争来获取。
并且和中央调度器不同的是,状态共享策略当中使用的是乐观锁。简单解释一下乐观锁和悲观锁的区别。悲观锁往往会假设最坏的情况,比如当下获取了资源之后,在使用结束之前有可能会有其他的线程来访问或者是修改,所以我们需要加上锁来避免这样的情况发生。
乐观锁则相反,基于乐观假设,也就是说系统是基于能够顺利运行不会有资源抢占的情况发生的前提进行的。也就是说先执行,执行之后如果发生了抢占或者其他问题,那么再来通过重试或者其他机制来解决。
即使在高并发场景当中,资源冲突也是一个相对来说的小概率时间。如果使用悲观锁显然会带来大量的加锁的开销,所以基于乐观锁的设计会使得系统的并发性能更强。但是这也是有代价的,乐观锁也不是完美的,如果真的发生了大量竞争冲突的情况,竞争失败的一方往往需要重试任务,这会带来很多不必要的开销,也会造成资源的浪费。
另外由于所有框架之间的抢占是自由的,也就是说有可能会出现高优先级的框架一直抢占资源,而低优先级的任务被饿死的情况发生。在这种机制下是没有办法保证任务之间的公平性的。这也是削弱中央调度器带来的必然后果。
总结
我们回顾一下以上三者策略,会发现这三者策略的演进顺序其实就是中央调度器被削弱的顺序。想想其实很容易理解,中央调度器强大可以维护整个集群的公平性,但是由于效率比较低,很有可能会成为整个集群的瓶颈。而中央调度器越弱,框架调度器的自由度越高,整个系统调度的灵活性越强,那么也就意味着系统的性能往往越好。
有人打了一个比方非常经典,集中式调度器有些像是计划经济。所有一切都由国家来规划,好处是可以保证公平,但是自由度和灵活性差,整个国家运转和发展都不够高效。两级调度器有些像是大政府小市场的混合模式,政府的干预力度还是很大,但是多了一点自由性。而状态共享器则是小政府大市场的自由竞争经济模式,政府的干预几乎没有了,变成了看不见的手,这样可以进一步提升灵活性和国家的运转效率。但是国家的干预少了,当风险降临的时候,也可能导致很大的问题。
某种程度上来说,和国家社会的制度没有完美无缺的一样,集群调度的策略目前来看也没有一个完美的,各有各的优势和特长,需要我们结合实际情况,寻找适合我们问题场景的最佳方案,这也是我们学习这些底层原理而不只是停留在如何使用上的原因。
今天的文章就是这些,如果觉得有所收获,请顺手点个在看或者转发吧,你们的举手之劳对我来说很重要。