00:01
在flink当中,除了按键分区状态state之外,另外一大类状态,那就是所谓的算死状态operator state,前面我们已经介绍过。Operator算子状态跟k set最大的区别就在于hit set是。按照K去进行隔离的当前的状态只针对当前K的数据有效,能够访问到,而对于算子状态而言呢,没有K的隔离。每一个分区子任务,它上面获取到的所有数据。都会访问到相同的同一份算子状态啊,那所以我们会发现算子状态某种意义上应该算是一种更为底层的状态类型了,它考虑的东西更少,那么它的功能呢,也不如按键分区状态k state那样的丰富,它的应用场景也会比较少,因为我们知道在绝大多数。
01:00
Flink流处理的场景里边,我们都需要首先先把数据进行分组K之后,然后再去进行聚合,进行开窗。算子状态它根本不考虑K,当然也就应用不到这些场景里面了啊,那所以呃,具体使用的过程呢,算子状态其实就像一个本地变量一样,当然了它使用的过程当中跟本地变量还是有所不同,那后面我们就具体的来讲解一下算子状态的概念和具体应用。首先,我们先来说一下算子状态的基本概念。所谓算子状态,其实就是一个算子并行实例上定义的状态,它的作用范围就被限定为当前的算子任务,那么算子状态呢?最主要的特点就是跟数据的K无关啊,所以不管它当前数据的K是什么,只要这些数据。
02:00
假如说我们前面有这个K操作。不管他的key是什么,只要它被分发到了同一个病情的。当前算子的子任务里面,那么他们就会访问到同一份算子状态。啊,那直观来看的话,我们就会发现这个很明显啊,在K之后要做的那些聚合窗口操作里边就都用不上了,那它一般用在哪儿呢?啊,它一般就用在。或者think这些跟外部系统进行连接的算子上,因为在这个时候我们还没有进行处理,那就还没有K的定义,对吧,在这种场景下。用算子状态可能就会比较高效,比较有用,比如说像flink跟卡夫卡进行连接的时候啊,Flink的卡夫卡连接器里边,它就用到了算子状态,如果我们给一个弗link卡夫卡的连接器,它的S算子设置并行度之后。
03:01
有了并行了,那么接下来卡夫卡consumer,我们知道它的连接之后的那个S算子是一个consumer,它要消费卡夫卡里面的数据,那么这个消费者的每一个并行实例,它都会为对应的topic主题分区,去维护一个偏移量,就是这个偏移量,我们知道它其实非常重要的,如果发生故障的话,我们应该能够恢复出来,然后去。重置偏移量,然后继续读取数据,这才有意义。所以这个偏移量是要保存起来的,它作为什么保存呢?就是作为算子状态保存起来的。这在后面我们会讲到link精确一次状态一致性保证这一点的时候非常的有用,这一部分我们会在后边继续做介绍。而另外还有一个就是说,当算子的并行度发生变化的时候,算子状态它也是支持并行的算子任务实例之间状态进行重组分配的,那根据算子状态它细分的类型不一样,那重组分配的方案也会有所不同。
04:09
那接下来我们就来介绍一下。算子状态到底有哪些具体的类型?之前我们说过k state,它里边有主要有值值状态、列表状态、映射状态,还有聚合状态这么几大类,那么算子状态里边是不是也有这么几类呢?算子状态,它支持的类型就非常的局限,非常少了。主要只有三类,那就是list state unionist state broadcast state。跟之前的的类型好像完全不一样了,那我们首先来看一下最基本也是最主要的算子状态。Lisa,那就是列表状态。这个列表状态看起来是唯一一个跟之前k state里边相同的那个类型了,那所以它跟k state里边的列表状态其实是一样的,也是把状态数据表示成为一个列表啊,就是所有相同类型的数据放在一起成为了一个列表状态。
05:09
那它跟K里边列表状态有什么区别呢?简单来讲就是还是没有K,那就是每一个并行子任务上只保留一个列表,当前并行子任务上所有数据来了之后,访问到的都是这同一个列表啊,那所以就是所有的状态下啊,都放在这个列表里边来了。那这里面涉及到一个问题,就是假如说我们接下来发生了并行度的改变,这个状态要重组分配的时候该怎么办呢?我们应该还记得,对于kid state而言,当时我们根本就没有考虑它的不同类型的状态到底怎么样去做重组分配。这是因为k state里面有一个概念叫做。的概念。
06:02
我们说在state里面。假如说有这样的并行子任务,那么不同的数据发送到了对应不同的并行子任务,每一个并行子任务里面呢,如果设置了k state的话,这个时候我们就会根据它当前的K。为每一个key。保存一个对应的状态实力。所以我们知道K一般它的数量非常多的,那么每一个并行子任务里边其实有很多个K的对应的状态实力,那在做这个对应重组调整的时候,那就非常简单嘛,因为它个数非常多。接下来你不管是。增大还是减小,其实都是把它打乱了,重新做一个分配就好了,当然了,具体在分配的过程当中还需要考虑,因为这个涉及到T,涉及到重新分区,那自然是还是要跟kid的哈希code要有一个关联啊,基于哈希kid哈西code,先整体做这样的一个分区,然后呢,还会涉及到之前K不是特别多吗?那我就把它划分成一个一个的建组。
07:12
然后按照不同的建组再把它们做一个重新分配,这是k state里边的考察它的具体的实现,那所以我们就会发现在T里边最关键的其实就是建。不管是直状态、列表状态还是。映射状态,聚合状态,它都跟K有关啊,那只要我按照这个把它划分了舰组,接下来自然而然的就可以把它重新分配了啊,但是我们发现现在在算子状态里边不能这么做了,因为现在跟K无关了,在算子状态里边每一个并行子任务就只有一个状态。所有数据来了之后,访问都是一份,那现在比方说我们当前两个并行子任务,有两个两份这个算子状态。
08:02
我们的并行都变成三了,你怎么能把两份状态再拆成三份呢?这就是一个问题了。所以。在算子状态里面,它连最简单的状态都没有。它就是为了避免这种情况,你如果要是有value的话,我这一个值下面又一个值,那接下来这两个值怎么分到三个上呢?没法拆分了,所以干脆。算子状态,就直接把所有的状态都定义成了一个列表,一个list。诶,那这个例子里边每一个状态,每一个状态,这不就又相当于之前Kate里边的建组了吗,每一组的状态了吗?那所以以这个列表里边的状态再作为一个基本单元,再进行一个重新分配,我们就又可以把它打散之后做重组。调整了,这就是算子状态进在并行度发生变化的时候,进行状态重新分配调整的一个基本的思路啊。
09:04
那接下来对于这个列表状态而言,它具体的分配策略又是什么样呢?其实也非常的简单,就是如果病情都发生调整了,缩放调整的时候,首先算子状态的列表所有元素像啊就被统一收集起来,也就是之前。有两个并行子任务的话,那么他的那两个列表现在就里边的所有元素都合到一起来。合成一个大的列表,然后假如接下来他要并行度调整成三的话,我就再把这个大的列表砍成三份分配下去就可以。这就是。当前算子任务的列表状态,它做调整的一个基本策略啊,那当然了,砍成三份之后,接下来怎么分配呢?那就是均匀的做一个轮询分配。这就跟我们之前讲到的数据传输的reb这种方式非常的类似,那就是逐一发牌,把所有的这个像之前我们说是砍成三份吧,怎么砍呢?诶,那是一个一个的发。
10:12
第一个发到。第一个分区子任务里边啊,那第二个发给第二个分区子任务,第三个发给第三个分区子任务,一次轮流发牌,它是这样的一个平均分配的过程。这就是关于列表状态的使用。那除了列表状态之外呢,还有联合列表状态,从名字上我们就会发现啊,它跟列表状态其实非常的类似,它也是表示成一个列表,那它跟最常规的list state有什么区别呢?那它的数据组织形式啊,和获取的这个接口这种方式都一样,最大的区别就在于。并行度缩放的时候,状态重新分配的策略不一样。那之前我们说list state状态重新分配呢,就是合成一个大的列表,然后发牌啊,轮群去做分配,平均分配,而Li UN呢,重点在于在于联合。
11:10
他怎么个联合法,那就是直接把。所有状态的完整列表全广播到重新调整的并行子任务里面去啊,所以像前面我们的这个例子里边。如果这里有两个并行子任务两个列表的话,那接下来。扩展成。并行部为三三个并行子任务的时候,他们就分别都会收到相同的两个列表,状态列表。那接下来。最后它到底剩下多少状态呢?哎,那就最终是由每一个分区子任务自行去进行选择,然后要丢弃哪些状态下。这种重组分配方式就叫做联合重组UN。
12:03
当然我们会发现,因为他要做这样的一个状态广播啊,如果说这个状态很大,状态像太多的话,很显然它的效率就会比较低,为了资源和效率的考虑,一般不建议轻易使用联合列表状态。那除了列表状态和联合列表状态之外,还有一种比较特殊的算子状态,那就是所谓的广播状态。这主要是考虑到什么场景呢?是有时候我们真的是希望每一个并行子任务,它都有相同的一份状态。诶,那这种场景我们在有些场景下确实是需要的,比方说相当于是我们的通用配置效,我们有一些配置项就应该是全局生效,那这个时候呢,我就不应该各自为政了,不应该只是维护自己的这一份了,但是我们说当前不同的并行子任务,它是分配到不同的节点上,至少是不同的slo上,它的资源内存资源是隔离的呀,没有办法跨slot去访问,那怎么办呢?诶,那就是把整个状态广播给所有的并行子人。
13:12
那所有的这个并行子任访问到的就相当于都是同一个状态。这种特殊的算子状态就叫做广播状态。这里边我们会发现,就是因为广播状态它每个并行子任务上这个状态实力都一样,所以呢,并行的调整就非常简单了,对吧?啊,就直接多复制一份,或者少复制一份,直接发送过去就完了嘛,啊这个就非常的简单,那在底层这个广播状态它是怎么样去保存的呢?其实是以一种类似于映射状态的结构啊,Map sit,类似于map sit这样的一个形式,直对的形式来进行保存的,而且这个广播状态必须基于一个广播流来创建。之前我们讲到connect。
14:02
双流河流操作的时候,其实简单的提到过,就是两条流做connect操作,一种方式是两条流连接在一起,另外一种方式呢,其实是一个data stream去连接另外一个广播流啊,那后边我们会详细的对广播状态再做一个讲解。这就是关于算子状态的基本介绍。
我来说两句