上一章节已经说过在Flink里面任务调度原理,这里面我们看到具体来说还有很多细节没有完整地说明白,比方说前面提到的TaskManager里面的slot到底是怎么去完成任务的,还有执行图到底做了哪些优化和调整呢,这些部分都是我们要考虑的问题,所以这一章节,把一些细化的概念做一些分析。
首先来说slots,在之前的学习,我们知道了每一个TaskManager都是干活的进程,它其实就是一JVM进程,那么在干活的过程中,一个JVM进程还能做多线程处理,所以它的内部是:在不同的线程上去执行一个或者多个它的子任务。而这批子任务,这个线程到底能执行多少个呢,为了控制内部线程运行子任务的个数,就提出了slots概念。slots就是TaskManager的固定大小资源的一个集合。ResourceManager在做资源分配管理的时候,最小的单位就是slot。那么接下来就看下ResourceManager是如何分配slot的。
一般的方式,我们按照每一个TaskManager机器的性能,它所含有的资源来配置slot。slot相当于它所有资源的一个子集,这个子集在执行过程中,就是一个隔离开的独立的子任务(线程)。相当于是用slot把不同的子任务之间做了一个隔离。如果机器的内存很大,cpu数量也多,那么就可以让它同时并行执行任务分配更多的slot。
第一点我们要注意的是,slot隔离主要是对内存的隔离,CPU本身是不做隔离的,CPU在不同的slot之间是可以共享的。slot内存是平均分配的,比如机器上有16G内存,如果划分4个slot的话,那每个slot就是4G内存了。如果每个slot内存太小的话,任务就执行不下去了,内存直接被撑爆这个是完全有可能的。
所以在划分内存方面需要我们去考量的,我们要根据我们执行任务的复杂程度,占用资源的角度和我们本身的机器它本身所有的资源大小做一个整体的分配。确定TaskManager到底分成几个slot。现在我们大部分机器其实内存还好,为了避免不同的slot之间共享CPU导致我们资源本身负载的程度不高,这时我们往往按照CPU的数量来分配多少个slot。
第二点要注意的是,默认情况下,Flink允许子任务共享slot,即使它们是不同人物的子任务。这样的结果是,一个slot可以保存作业的整个管道。Task Slot是静态的概念,是指TaskManager具有并发执行能力。
slot分配目的:我们其实是想要资源能够更平均地分配到我们的任务上,能够让我们的资源利用率能够更高。
举例说明,比如在我们所有的算子操作里面,有一些Source和map这样的任务,它相对比较简单,我们把它们叫做非资源密集型的,对资源占用率不是那么高,另外一些任务,比如keyBy()/window()这些聚合操作相对来讲,资源占用率较高,它是资源密集型任务。所以为了防止非资源密集型任务一直处在空闲的状态(等待状态),为了让这些空闲的资源能够利用起来,我们可以将所有的子任务都分配到各个slot上,这样每个slot里有资源密集型任务和非资源密集型任务。都会有简单和复杂的任务,这样所有的slot资源利用率都差不多,就都不会空闲了。
允许slot共享的好处:
1.可以获得更好的资源利用率,不会出现slot之间闲的闲,累的累这种情况。可以让他们每一个slot都差不多忙,具体每个slot里面先忙什么后忙什么那就看自己调度看执行顺序了。
2.JobManager拿到任务执行计划后,它如何确定到底需要多少个slot,这时它只要看整个作业里面,并行度最高的那个算子设置的并行度就可以了,只要满足它的需求,别的就都能满足了。
下面图片展示了TaskManager分配slot任务和优化后的对比情况:
优化前:
优化后:
优化举例:
假如我们有3个TaskManager,我们的机器能力是每个TaskManager能够分配3个slot,那么一共就是9个slot,而如果我们设置一个slot,那么任务分配情况如下图:
我们可以看到,有9个slot,只用到了一个slot,这显然分配不均匀。
如果我们全局设置并行度为2,那么所有的任务都被分到了两个slot里。如下图:
可以看到,还有空白的slot,这个效果也不是很好
如果要分配充分一些,我们要充分利用它的并行的能力,我们应该把并行度设置成9,它的并发度就能够得到最大的利用率,如下图:
这样,并发度看起来就都利用起来了,但这样最后就是最好的吗,并不是的。因为我们看到sink算子,是做输出的,假如这个sink是写入外部文件,那么这个sink并行在9个slot里,并行地写,那么这个文件可能就会出现一个问题,我们要写的数据就乱了。这时我们要做的是把sink并行度调低,我们可以把sink的并行度单独设置为1,如下图:
这样,最后的状态是前面两个任务,所有的并行度都是9,而只有最后一个sink任务并行度是1.