Talking about some details of distributed cluster management system 【二】

This article originated from a personal public account : TechFlow , original is not easy, seek attention


Today is the twelfth article of the distributed topic . Let us continue to look at the cluster resource management system.

In the last article, we briefly learned what is distributed cluster resource management, its birth background and what problems it solves, and what are its advantages and disadvantages. The content of the previous chapter is relatively superficial, without too much in-depth principle. In this article, let's take a look at the principle part of the cluster management system .

If you have forgotten an article, or if you are the latest follower , you can click the portal below to review the previous article.

On the principle of distributed cluster resource management system

Local priority

In the context of big data applications, there is a basic design principle: we usually assign the calculation to the node that stores the data for execution , rather than get the required data from the node and then perform the calculation. The reason behind this is easy to figure out, because it can minimize network communication between nodes and reduce data transmission. It is important to know that the scale of data in the big data scenario is very large, ranging from TB, PB, and sometimes hundreds to tens of GB. Once the network transmission data is required, the overhead is very considerable.

We summarize this principle, which can be called the " local priority " principle, that is, the more local the node performing the task, the better , if it is too perfect in a physical machine, because it can avoid all data transmission.

We can simply measure the capacity of a cluster scheduling system according to this principle, and we can simply list three levels according to the locality. The first is node locality , that is, all calculations take place in one node, so that all data transmission can be avoided, which is also the best case. The second difference is called rack locality , which means that although all calculations cannot be performed in one node, at least they can be guaranteed that the executing machines are in the same rack. The machines in the rack can transfer data internally without having to resort to external networks, and such transmission is much faster. The worst case is that the nodes are scattered in different racks and there is no way to do any acceleration. This is called global locality and will bring a lot of overhead.

We all know that a good cluster scheduling system must " squeeze " the performance of all the machines in the cluster to the extreme, but in fact the computing resources consumed by fixed computing are basically stable. In addition to the utilization rate of the machine , another key point is this. And sometimes the overhead caused by network IO may be more terrible than the low usage of the machine.

Resource allocation details

About resource allocation, our intuitive understanding may be very simple. When a machine is idle, we will assign it to a task, and then execute it. We will recover the resource, but there are still many details in the actual application that require careful design and thinking . A little bit of failure may cause serious consequences.

Let's look at two questions. The first question is what should we do when we have a new task now, but there are not enough resources ?

我们很容易想到有两种策略,第一种策略就是什么也不做,。等到有任务结束了,资源释放出来了之后,我们再执行当前这个任务。但如果我们当前的任务是一个非常紧急的任务呢?有可能现在正在执行的任务优先级并不高,但是占用的资源又多又长,难道我们还要一直等下去吗?

所以我们会想到还有第二者策略,就是。既然我们当下的资源优先级高,正在执行的任务优先级低。我们可以先从优先级低的任务当中抢一部分资源过来,先把当下重要的任务执行了再说。等优先级高的任务执行了之后再去执行优先级低的。但是很遗憾,这么做是有问题的。技术上的问题我们先不谈,等会再说,有没有想过这么搞的话,还会有谁愿意把任务的优先级调低呢?肯定大家执行的任务都是最高优先级。到后来会发现所谓的优先级高低设置全部变成了摆设,正在运行的优先级都一样,都是最高优先级。

关于上面的问题还有后续,我们先放一放,来看另外一个问题,这个问题是上面这个问题的衍生品。不同的任务需要的资源不同,并且需要资源的情况也不相同。比如有些任务资源多少都可以运行,比如spark或者是MapReduce,资源多就多用几个机器,资源少就少用几个,但是不管多少都可以跑,无非是执行的时间长短不同。但是有些任务就不是如此,比如机器学习的任务,可能一次性需要大量的资源,并且就要这么多,少了就跑不了。那么问题来了,当我们新来了一个任务,当下资源不足以满足它分配需要的时候,我们是先留着不分配给它,等资源足够了一次性分配呢,还是先分配一点,后面拿到一点资源再继续分配呢?

你看,看起来平淡无奇的分配策略其实当中还是有很多棘手的细节。事实也的确如此,这也是为什么我之前说目前的集群资源管理系统还远远没有成熟才刚刚起步的原因,因为对于以上的问题都没有特别好的解决方案。

饿死与死锁

还记得上面的两个问题么,如果这两个问题没有回答好,那么就会出现饿死与死锁的情况。

饿死是指任务一直得不到调度,比如由于设置的优先级不合理。只有你一个老实孩子给自己觉得一个不太重要的任务设置了一个正常的优先级,而其他的老司机纷纷给自己的任务设置了最高优先级。由于一直有高优先级的任务被提交进来,所以你的任务被一直延后,你以为很快就能得到结果,可能到下班你的任务还没开始执行。在这种情况下,你要么同流合污,也把自己的任务设置成最高优先级,要么就只能一直等下去,或者因为完成的工作不够多而面临绩效以及老板的压力。

于是,这就从了一个简单的任务调度的问题上升到了内心价值观的考验,这也是一个经典的劣币驱逐良币的过程。由于少部分人的不遵守规则,导致反倒是遵守规则的人受到惩罚,想想真是男默女泪[狗头]。

死锁的情况比较容易理解,如果学过操作系统的同学应该很熟悉,原理是一样的。打个比方,如果我们当下有AB两个任务,这两个任务都需要集群2/3的资源才可以执行。由于这两个任务是差不多时间点提交的,而系统采取了先来先分配的原则,给这两个任务都分配了1/2的资源。那么这就会导致死锁,因为这两个任务没有一个能执行,也就没有一个任务会释放资源,所以除非人工kill掉一个,否则会一直如此持续下去。

从目前的情况来看,好像没有一个完美的方法可以完全避免这两种情况出现。只能架构师根据自己集群调用的实际情况进行决策,并且还要加入一些人工干预的因素,比如在团队当中约法三章制定一些规范和条例等等。也就是说,某种程度上来说这已经不只是一个系统的问题了,而是一个系统和团队协调的复杂问题。

调度器

下面我们来看看常见的调度器的架构,常见的调度器有三种,第一种是集中式调度器,第二种是两级调度器,最后一种是状态共享调度器

集中式调度器

我们先来看集中式调度器,它最直观也最简单:

它的设计逻辑就是集中,整个系统当中只有一个全局的中央调度器。所有的框架或者是计算任务都由中央调度器来实现。有点像是封建时候的宗族制度,整个大家族当中所有的大事小事全部由一个人来管理。显然这样做的弊病非常多,刚才提到的两个问题都需要人工干预,否则很难解决。

后来在此基础上进行了改进,在整个中央调度器当中又加上了分支逻辑。这种调度器被称为多路径调度器:

整体而言变化不大,只是多了一个条件判断。也就是说内部实现了针对不同种类的任务执行不同的调度和分配策略。比如如果是小的碎片任务,那么执行优先级管理,先来先得策略。如果是大的机器学习任务,只有拿到完整资源才会执行,防止死锁等等。

相比于单路径集中调度而言,多路径集中调度增加了一些灵活性,但是整体的拓展性还是远远不够,并且并发能力也比较差,资源的利用率不够高,如果规模大了,调度性能很容易成为瓶颈。但是它胜在架构简单,容易维护。

两级调度器

由于集中式调度器有许多的问题,也不够灵活,我们为了增加它的灵活性,在此基础上又增加了一层结构:

我们同样有一个中央调度器来坐镇指挥,只不过中央调度器并不会直接调度任务,而是会以一种比较粗粒度的策略将集群当中的资源分配给框架调度器。具体调度、执行任务的逻辑在框架调度器当中。相比于中央调度器,框架调度器执行的策略会更加细粒度一些。

另外,只有中央调度器可以看到整个集群当中所有资源的情况,框架调度器只能看到自己分配到的那一部分资源。我们比较熟悉的YARN,Mesos都是采取的这种架构。

有了框架调度器之后,我们可以在不同的框架调度器当中执行不同的策略。有助于提升整个集群的并发能力,以及资源利用率。所以总体而言,两级式调度器的性能要比集中式调度器好得多。

但是即使是这样,也不是完美的。因为中央调度器在调度的时候执行的是一种悲观并发的策略。简单解释就是在执行分配的过程当中,中央调度器会严格按照事先制定的顺序,并且会对资源加锁来防止不同框架申请资源时导致的冲突。既然用到了悲观锁,显然也会影响整体的性能。

状态共享调度器

状态共享调度器的架构和两级调度器非常接近,可以简单理解成了去掉了中央调度器的结果:

这个架构最早出现在Google公司的Omega调度系统当中,Omega调度系统是现在非常火热的Kubernetes的前身。它和两级式调度器的最大的不同就是没有中央调度器,所有的框架调度器可以直接看到整个集群当中的所有资源。在需要使用资源的时候,由框架调度器之间互相竞争来获取。

并且和中央调度器不同的是,状态共享策略当中使用的是乐观锁。简单解释一下乐观锁和悲观锁的区别。悲观锁往往会假设最坏的情况,比如当下获取了资源之后,在使用结束之前有可能会有其他的线程来访问或者是修改,所以我们需要加上锁来避免这样的情况发生。

乐观锁则相反,基于乐观假设,也就是说系统是基于能够顺利运行不会有资源抢占的情况发生的前提进行的。也就是说先执行,执行之后如果发生了抢占或者其他问题,那么再来通过重试或者其他机制来解决

即使在高并发场景当中,资源冲突也是一个相对来说的小概率时间。如果使用悲观锁显然会带来大量的加锁的开销,所以基于乐观锁的设计会使得系统的并发性能更强。但是这也是有代价的,乐观锁也不是完美的,如果真的发生了大量竞争冲突的情况,竞争失败的一方往往需要重试任务,这会带来很多不必要的开销,也会造成资源的浪费

另外由于所有框架之间的抢占是自由的,也就是说有可能会出现高优先级的框架一直抢占资源,而低优先级的任务被饿死的情况发生。在这种机制下是没有办法保证任务之间的公平性的。这也是削弱中央调度器带来的必然后果。

总结

我们回顾一下以上三者策略,会发现这三者策略的演进顺序其实就是中央调度器被削弱的顺序。想想其实很容易理解,中央调度器强大可以维护整个集群的公平性,但是由于效率比较低,很有可能会成为整个集群的瓶颈。而中央调度器越弱,框架调度器的自由度越高,整个系统调度的灵活性越强,那么也就意味着系统的性能往往越好。

有人打了一个比方非常经典,集中式调度器有些像是计划经济。所有一切都由国家来规划,好处是可以保证公平,但是自由度和灵活性差,整个国家运转和发展都不够高效。两级调度器有些像是大政府小市场的混合模式,政府的干预力度还是很大,但是多了一点自由性。而状态共享器则是小政府大市场的自由竞争经济模式,政府的干预几乎没有了,变成了看不见的手,这样可以进一步提升灵活性和国家的运转效率。但是国家的干预少了,当风险降临的时候,也可能导致很大的问题。

某种程度上来说,和国家社会的制度没有完美无缺的一样,集群调度的策略目前来看也没有一个完美的,各有各的优势和特长,需要我们结合实际情况,寻找适合我们问题场景的最佳方案,这也是我们学习这些底层原理而不只是停留在如何使用上的原因。

今天的文章就是这些,如果觉得有所收获,请顺手点个关注或者转发吧,你们的举手之劳对我来说很重要。

Guess you like

Origin www.cnblogs.com/techflow/p/12679209.html