00:00
到目前为止,关于k state这一部分的内容我们就都已经介绍完了啊,那kit确实是我们整个状态编程学习过程当中的一个重点,因为在实际应用的过程当中,往往我们都是先做KBY,然后针对当前的K去进行相应的逻辑设置的,所以一般我们用到的状态都是kid state。那接下来呢?呃,除了k state之外,我们还要介绍另外一大类manager state,那就是所谓的算子状态operator state。呃,前面我们也提到过算子状态跟k state的区别,主要的区分就在于算子状态是跟当前数据的K没有关系的。每一个分区并行子任务,那么就会维护着一个状态实例,所以在同一个分区里边的所有数据都能够访问到相同的一份状态,跟K没关,那所以呢,算子状态的作用范围就没有k state那么精细了啊,那就是所有数据其实过来访问到的都是一样,从代码上来讲的话,我们可以认为一个算子状态就跟一个本地变量的作用范围是完全一样,这个我们可以简单测试一下啊,就像之前我们在K的这个测试文件里边定义了很多不同的状态,不同类型的状态,那在这里如果要做对比的话,我们可以。
01:30
定义一个本地属性。定义一个属性,这就相当于是一个变量了啊啊,那比方说我们这个叫做count。后面我们好像单独定义了有这个抗变量啊呃,那那这里我们换一个名字。比如说就叫做local count。啊,那初始值的话直接给他一个零,然后接下来前面我们不是这个map state,它里边的这个计数,我们就是每来一个加一,另外a state这个aggreating state啊,聚合状态也是每来一个就加一吗?注意我们这些都是kid state,所以是针对当前key,我们当时是以用户名作为分组,那就是针对当前用户,每来一个even的事件就加一。
02:17
那现在假如说我们再定义一个local count,它也是每来一个就加一,我们在flat map这个方法里边啊,最下边把local count。也做一个处理,哎,那每一次都加一,然后打印一下它的值。我们可以运行一下,看一看它跟前面我们提到的这些k state有什么区别。这就相当于是一个本地变量啊,啊,我们看到聚合状态,诶一个一个啊,按照不同的用户。那么我们看凯瑞来了一条数据,那么它的聚合状态一开始是一,访问频次是一啊,然后又来一条凯瑞的数据,当然聚合状态是二,访问频次是二啊,Bob的来了一条数据,那么Bob按照当前用户的访问事件,它的聚合状态是一,访问频次是一,但是注意我们这里的local count呢?
03:08
Local count并不管他到底是哪个用户,直接所有的数据来了之后都会加一,所以我们看就是每一条数据来了之后,当前这个local count都会不停的往上加。所以从效果上来讲啊,一个算子状态,它的作用范围跟这里我们提到的这个定义的本地属性,或者说本地变量是可以说是完全一样的啊,它都是针对所有的数据到来之后啊,跟K无关,所有数据都会引发它的变化。这就是关于算子状态和k state最大的一个区别啊,那当然了,前面我们讲状态管理的时候曾经提到,我们主要在这个分布式流处理场景下要解决三个问题,一个是访问权限啊,那访问权限的话,K解决了不同K能够访问不同的状态实例这样一个问题,那算子状态呢,就不用解决这个问题了,所有数据都访问同一个状态,那另外还有两个问题,一个是容错性,发生故障要能把它恢复,那另外还有一个呢,就是分布式应用的横向扩展性,也就是并行度调整的时候,状态到底应该怎么分配?
04:20
那关于这两个问题容错性的话,前面我们也提到了啊,在弗林克底层,它所使用的就是检查点这个机制,来保证发生故障之后能够正确恢复的。所以前面我们说从访问范围作用范围来看的话,一个算子状态看起来就跟一个本地变量没什么区别,哎,那它的真正意义上区别在哪里呢?区别就在于算子状态还必须要实现一个checkpoint的方式接口,然后在里边我们要把算子状态跟一个本地变量要关联起来,也就是说相当于我们在做检查点保存的时候,要知道怎么样对这个状态进行保存,然后另外就是发生故障去恢复的时候,怎么样把这个状态再恢复出来啊,啊,所以这里边后面我们在代码当中重点就是实现这个接口。
05:12
那所以我们也就说了,算子状态它的应用场景一般都是没有k state那么多的啊,一般我们处理核心的业务逻辑的时候,都使用的是k state,那算子状态一般用在什么地方呢?哎,那就是用在没有K定义的场景下,或者说像跟外部系统要做连接的时候,Source算子或者think算子,这个里边如果要定义状态的话,有时候就会用operator state。那前面我们说到状态管理还有最后一个问题,就是考虑并行度发生改变的时候,当前状态怎么样去重新分配?之前我们说对于k state而言,它是因为不同的K都有自己对应的一份状态实力,所以哎,我们就可以先把它划分成不同的建组,然后啊,再进行重新整合,重新分配,那所有的K放在一起,我们就重新调整,就有了对应的这个分配的机制了。
06:13
那对于算子状态来讲,现在好像就已经没有不同的状态实例让我们去进行分割了啊,那比方说现在本身它是两个并行度,现在并行度如果变成三的话,怎么能把之前的两份状态拆分成三份呢?那这个看起来就稍微有点麻烦啊。所以对于算子状态而言,它还是要看具体的状态类型到底是什么样的,而对于段子状态而言呢,它所支持的状态类型比k state就要少很多。啊,所以接下来呢,我们再来说一下这个算子状态的类型。那算子状态里边主要就有三种类型,List state union list state和broadcast state啊,那所以我们看到啊,这里边算子状态已经没有最简单的值状态了,Value已经没有了啊,它主要的类型其实就是列表状态。
07:06
那这里的列表状态呢?本质上跟kit里边的列表状态是一样的,就是把状态表示成一组数据的列表。那算子状态里边的Li set,当然跟k state最大的不同就是跟K无关,当前的一个分区子任务上就只有这样的一个列表,哎,那所有的数据,我们要保存的状态都放在这一个列表里面,哎,那我们会想到当他想要去做这个并行度调整的时候,状态重新分配的时候怎么办呢?啊,因为是列表嘛,如果并行度是二,那这个就简单了,我们把这个列表原来是有两份,我们把它合起来再平均分成三份不就完了吗?哎,那所以这样的话,我们就可以进行并行度调整时候的状态重新划分了。所以从这个角度我们也可以看得出来,为什么算子状态里边没有直接设置value state这样最简单的值类型啊,因为如果是一个值的话,那就没有办法做并行度调整啊,如果是list的话,这就相当于还可以再划分了,还可以进行调整啊,所以它最简单的就是list state,然后另外还有一个union list state,这是什么意思呢?整体来说的话,跟list state是一样的啊,它的表示形式也是一个列表,那主要的区别就在于定型度调整的时候,到底是什么样的一个调整机制,那对于前面我们讲到的这个list state而言,它本身要做调整的时候就是直接轮询,就平均分配,那就是我们说的啊,原先并行度是二。
08:45
那每一个并行子任务有这样的一个列表,里边存了很多个状态。那另外一个分区子任务也存了很多个,那接下来如果并行都变成三的话,所有的这个状态怎么平均分配到三个并行子任务上呢?直接轮取,哎,第一个来这边,第二个元素来这边,第三个元素来这边,依次分配就可以了,这是列表状态的分配方式,那如果是联合列表状态呢?Union states呢?哎,它就不是这样做轮询了,它是直接把原先的这两个列表。
09:20
合并在一起,然后得到了一个完整的大列表啊,然后接下来呢,直接全部发送给下游,呃,就是发生并行度调整之后的所有并行子任,让他们自己去选择我要保留哪些对应的状态,哎,那所以这样的话,它就相当于灵活度啊,可选的自由度更高,但是如果说我们这个列表当中状态项太多的话,可能资源的耗费就会更大啊,因为它要同时发给所有的并行子任务啊啊,那效率可能也会低一点,所以一般情况啊,我们平常使用算子状态的时候,用的就是list state UN list state用的比较少。
10:01
那除了这两种之外呢?另外还有一个比较特殊的算子状态,叫做广播状态broadcast state,这个前面我们也提到了,所谓的广播是什么意思呢?那就是所有的并行子任务,当前有两个并行子任务,他们的状态。都是一样的,长得完全相同,所以看起来呢,就好像所有的数据到了这一步任务做操作的时候,好像访问到的都是同一份状态一样。啊,那在这种场景下呢,如果要做并行动调整,那就更简单了,因为我们的每一份状态都一样吧,那你如果二调整成三的话,那就相当于多复制一份状态,然后给第三个多出来的那个并行子任务不就完了吗?哎,那所以这种方式的话,调整并行度的时候就会比较简单,那广播状态一般是用在。就是前面我们提到的广播连接流里边会使用到它,所以这个我会放在后边单独进行讲解。
11:02
这就是关于算子状态的概念和分类,我们已经做了一个整体的介绍,有了直观的认识。
我来说两句