Flink作业链与处理槽共享组(slot-sharing-group)区别

概述

其实这两个概念我们可以看作:资源共享链与资源共享组。当我们编写完一个Flink程序,从Client开始执行——>JobManager——>TaskManager——>Slot启动并执行Task的过程中,会对我们提交的执行计划进行优化,其中有两个比较重要的优化过程是:任务链与处理槽共享组,前者是对执行效率的优化,后者是对内存资源的优化。

作业链

一、执行过程

                                   Flink作业链与处理槽共享组(slot-sharing-group)区别

  • Chain:Flink会尽可能地将多个operator链接(chain)在一起形成一个task pipline。每个task pipline在一个线程中执行
  • 优点:它能减少线程之间的切换,减少消息的序列化/反序列化,减少数据在缓冲区的交换,减少了延迟的同时提高整体的吞吐量。
  • 概述:在StreamGraph转换为JobGraph过程中,关键在于将多个 StreamNode 优化为一个 JobVertex,对应的 StreamEdge 则转化为 JobEdge,并且 JobVertex 和 JobEdge 之间通过 IntermediateDataSet (中间数据集)形成一个生产者和消费者的连接关系。每个JobVertex就是JobManger的一个任务调度单位(任务Task)。为了避免在这个过程中将关联性很强的几个StreamNode(算子)放到不同JobVertex(Task)中,从而导致因为Task执行产生的效率问题(数据交换(网络传输)、线程上下文切换),Flink会在StreamGraph转换为JobGraph过程中将可以优化的算子合并为一个算子链(也就是形成一个Task)。这样就可以把这条链上的算子放到一个线程中去执行,这样就提高了任务执行效率。

可见,StreamGraph转换为JobGraph过程中,实际上是逐条审查每一个StreamEdge和该SteamEdge两头连接的两个StreamNode的特性,来决定该StreamEdge两头的StreamNode是不是可以合并在一起形成算子链。这个判断过程flink给出了明确的规则,我们看一下StreamingJobGraphGenerator中的isChainable()方法:

                                      Flink作业链与处理槽共享组(slot-sharing-group)区别

该方法返回true时两个端点才可以合并到一起,根据源码我们可以得出形成作业链的规则如下:

  1. 上下游的并行度一致(槽一致)
  2. 该节点必须要有上游节点跟下游节点;
  3. 下游StreamNode的输入StreamEdge只能有一个) 
  4. 上下游节点都在同一个 slot group 中(下面会解释 slot group) 
  5. 下游节点的 chain 策略为 ALWAYS(可以与上下游链接,map、flatmap、filter等默认是ALWAYS) 
  6. 上游节点的 chain 策略为 ALWAYS 或 HEAD(只能与下游链接,不能与上游链接,Source默认是HEAD) 
  7. 上下游算子之间没有数据shuffle (数据分区方式是 forward) 
  8. 用户没有禁用 chain 

二、开启/禁用全局作业链

用户能够通过禁用全局作业链的操作来关闭整个Flink的作业链,但是这个操作会影响到这个作业的执行情况,除非我们非常清楚作业的执行过程,否则不建议这么做:StreamExecutionEnvironment.disableOperatorChaining()。全局作业链关闭之后,如果想创建对应Operator的作业链,可以使用startNewChain()方法:someStream.filter(...).map(...).startNewChain().map(...)。注意该方法只对当前操作符及之后的操作符有效,所以上述代码只对两个map进行链条绑定。

三、禁用局部作业链

如果我们只想对某个算子执行禁用作业链,只需调用disableChaining()方法:someSteam.map().disableChaining().filter(),该方法只会禁用当前算子的链条(上述代码中就是map),对其他算子操作不产生影响。

处理槽共享组(出于某中目的将多个Task放到同一个slot中执行)

一、Task Slot

 TaskManager 是一个 JVM 进程,并会以独立的线程来执行一个task。为了控制一个 TaskManager 能接受多少个 task,Flink 提出了 Task Slot 的概念,通过 Task Slot 来定义Flink 中的计算资源。solt 对TaskManager内存进行平均分配,每个solt内存都相同,加起来和等于TaskManager可用内存,但是仅仅对内存做了隔离,并没有对cpu进行隔离。将资源 slot 化意味着来自不同job的task不会为了内存而竞争,而是每个task都拥有一定数量的内存储备。

通过调整 task slot 的数量,用户可以定义task之间是如何相互隔离的。每个 TaskManager 有一个slot,也就意味着每个task运行在独立的 JVM 中。每个 TaskManager 有多个slot的话,也就是说多个task运行在同一个JVM中。而在同一个JVM进程中的task,可以共享TCP连接(基于多路复用)和心跳消息,可以减少数据的网络传输。也能共享一些数据结构,一定程度上减少了每个task的消耗。

二、共享槽

一个TaskManager中至少有一个插槽slot,每个插槽均分内存并且之间是内存隔离的,但是共享CPU。算子根据计算复杂度可以分为资源密集型与非资源密集型算子(可以认为有的算子计算时内存需求大,有些算子内存需求小)。现在有这么个情况:某个Job下的Tasks中既有资源密集型Task(A),又有非资源密集型Task(B),他们被分到不同的slot上,这就会产生一个问题,有的slot内存使用率大,有的slot内存使用率小,这样就很不公平,内存没有得到充分的利用。所以我们可以采用一个方案:将A、B放到同一个slot当中。

默认情况下,Flink 允许subtasks共享slot,条件是它们都来自同一个Job的不同task的subtask。结果可能一个slot持有该job的整个pipeline。允许槽共享,会有以下两个方面的好处:

  • flink计算一个job所需slot数量时,只需要确定所其最大并行度(前提,保持默认SlotSharingGroup),而不用计算每一个任务的并行度的总和;
  • 能更好的利用资源,如果没有solt共享,那些资源需求不大的map子任务将和资源需求更大的window占用相同的资源。

Flink相同资源组里的多个Task可以共享一个Slot资源槽。具体共享机制又分两种:

1、CoLocationGroup强制将 subtasks 放到同一个 slot 中,是一种硬约束

  • 保证把JobVertices的第n个运行实例和其他相同组内的JobVertices第n个实例运作在相同的slot中(所有的并行度相同的subTasks运行在同一个slot );
  • 主要用于迭代流(训练机器学习模型) ,用来保证迭代头与迭代尾的第i个subtask能被调度到同一个TaskManager上。

2、SlotSharingGroup: 允许不同的JobVertices的部署在相同的Slot中,但这是一种宽约束,只是尽量做到不能完全保证

  • SlotSharingGroup是Flink中用来实现slot共享的类,它尽可能地让subTasks共享一个slot; 
  • 保证同一个group的并行度相同的sub-tasks 共享同一个slots ;
  • 算子的默认group为default(即默认一个job下的subtask都可以共享一个slot) 
  • 为了防止不合理的共享,用户可以强制指定operator的共享组,比如: someStream.filter(...).slotSharingGroup("group1");就强制指定了filter的slot共享组为group1;
  • 要想确定一个未做SlotSharingGroup设置的算子的group是什么,可以根据上游算子的 group 和自身是否设置 group共同确定; 
  • 适当设置可以减少每个slot运行的线程数,从而整体上减少机器的负载。