00:00
所以现在我们已经了解了,Flink当中为我们提供了一整套的状态管理机制,主要就是为了解决分布式流处理中出现的这些问题,那接下来呢,我们就来介绍一下具体在flink当中状态有哪些类型。哎,那首先呢,整体来讲,状态可以分成两大类,一类叫做manage state,另外一类叫做roll state,啊翻译的话,那manage state就是托管状态,State就是原始状态。那它俩的区别呢?呃,字面上可以说是一目了然,托管状态,那就是被link统一管理起来的,一系列的状态,那就是所有这些状态的存储和访问,发生故障之后的恢复啊,遇到并行度调整之后的重组,所有这些问题都是被link帮我们统一管理起来了。我们只要调用对应的接口,然后接下来所有的事情都不需要我们操心,而所谓的原始状态呢,哎,这个就真的是相当于是我们本地内存里边的一段数据了。
01:05
Flink什么都不管,所有的东西都要我们自定义去进行实现,那所以整体来讲的话,一般情况肯定我们都不会去用这个原始状态啊,我们都是让弗link帮我们统一进行管理的,所以接下来我们介绍的主要内容都是基于manager state。而member state又能分成哪些类型呢?诶,那简单来讲的话,如果按照状态底层的数据结构哎,我们可以大概的分成值状态、列表状态、映射状态、聚合状态等等不同的数据结构啊。那值状态的话,前面我们也接触到了啊,Value state,它就是一个单独的值,至于这个值本身的数据类型又是什么样,我们可以对它定义里边的泛型啊,那另外呢,我们还可以定义列表状态啊,就是保存成一张列表啊list,那另外还可以定义映射状态,这是一个map啊,另外还可以有聚合状态everygg state。
02:00
那所有的这些托管状态呢?呃,其实整体来讲,按照他们的访问范围,可见范围又可以分成两大类,那就是所谓的算子状态和案件分区状态operator state和k state。哎,所以一看这个名字的划分我们就知道了,这又跟当前的key有关系了啊,因为前面我们提到啊,分布式的流处理里边对于状态的控制最大的一个问题不就是状态的访问权限吗?哎,我们说你当前这个K的分配并不是说一个K一个分区,那这个时候你在当前这个分区上有很多个K都能访问同样的状态,怎么解决这个问题呢?哎,那我们自然想到了,你就按照不同的K对它进行一个单独保存不就完了吗?哎,所以如果说对于K不同的key有单独的状态对应的实例的话,那这种状态就叫做按键分区状态k state。那如果说没有这样单独的定义的话,那就叫做算子状态operator state啊,首先我们先来看算子状态啊,Operator state其实是我们最容易理解的一种状态了,哎,那就是它的状态作用范围就限定在当前算子任务的实例上,也就是说我们当前一个任务task,比方说我们这里就是一步map吧,我们可以设置一个并行度,并行度是二的话,当前map操作就会有两个并行的子任务。
03:25
那么如果说我们对于当前这个任务设置了一个状态的话。那每一个并行的子任务,它占据一个分区,这个分区里边对应的内存里边就会有我们设置的一个状态实例,啊,那所以对于当前这个分区里边所有的数据而言,他们都会访问到相同的一份状态。所以从这个角度来看的话,这样一个算子状态,它就跟我们当前这个分区里边的一个本地变量是非常类似的啊,你调用的时候其实也就是一样的啊,拿出来直接用就可以了啊,然后每个数据来了之后都会访问到,都可以去进行更改。
04:08
那它跟本地变量有什么区别呢?诶,它的主要区别其实就在于如果真的是内存里边的本地变量的话,诶,那我们定义出来之后,后面没有任何的持久化保存的策略,如果掉店当然就丢了,好接下来你还是啊,重新创建实例,然后就重新开始了,那我们现在的状态呢,那是需要有容错性保证的,就是发生故障之后还得能恢复的,所以得有一个持久化保存的机制,那这个机制呢,在弗link当中是所谓的检查点。Checkpoint。啊,那这个机制我们会在后边的第十章。专门在进行讲解。现在我们介绍算子状态的话,它在代码当中最大的一个区别其实就是实现一个所谓的checkpoint的方式接口,然后接下来就可以对状态进行持久化。这是关于算子状态,那另外还有一大类就是按键分区状态k state k state的话,我们自然就想到它是按照当前我们流里边所定义的K来对于状态进行了一个单独的划分。所以接下来呢,对于状态的访问就不是每一个分区所有的数据都可以访问到同样的状态了,而是尽管进入的是同一个分区。
05:27
接下来,如果说当前数据的K不一样,他们访问到的就只是。自己那个K对应的一份状态实例。那这样的话就解决了我们之前那个问题,诶T败之后,如果说我们要做一个sum统计的话,现在就不会搞混了,我们统计的诶,比方说就是A出现了几次,B出现了几次,如果A和B作为T的话,数据都发送到了我们这里的第一个分区的话,现在他们就不会搞混了,因为A的状态是单独的一份,B的状态是另外一份。
06:00
啊,其实我们想到这个底层的话要保存,这就相当于是一个哈希表嘛,一个哈希map,一个K一个value,一个K一个value,这样的话就可以分开不同T对应的状态值了。啊,那在实际的使用过程当中,如果说我们想要去定义一个k state的话,很明显那必须得有键才可以,所以必须在TBY之后进行有状态计算的算子才可以定义k state啊,那比如说之前我们所说的。聚合计算啊,像我们直接做一个reduce k by之后做reduce的话,它在本地就会保存一个k state,哎,那同样后面我们说到的窗口计算啊,KBY之后做开窗,然后做聚合,那么聚合的过程当中,它的所有状态是什么类型呢?哎,那都是根据当前的K进行划分的,所以就都是这里的kid state都是这种类型。另外呢,就是我们之前在代码里边去实现的那样,在当前的运行时,上下文里边可以直接获取到状态控制句柄,这样的话就可以去自定义状态,然后进行状态编程了,那这里要注意,只有获取到运行上下文才能做这些操作,那所以我们这里的定义呢,就只能在负函数类里面去实现啊,那这里像我们定义这个key process方式的话,它本身是处理函数,处理函数是继成字抽象负函数类的,所以这里面我们同样也可以实现自定义的状态。
07:33
那既然这么说,哎,我们自然就想到了,那假如说啊,我们之前说过,这个算子任务不是可以分成两大类吗?一大类是没有状态的算子,另外一大类是有状态的算子。之前我们说map filter flat map,它们都属于无状态的算子,那确实我们直接实现一个map function里边并没有任何涉及到状态的地方,还有那个map转换,也就是一个输入一个输出就完了嘛。但是现在我们发现,如果我们要是实现的是一个reach map function的话。
08:05
那情况就有所不同了,如果是负函数类的话,我们可以获取到运行时上下文,那自然就可以在运行上下文里面去做自定义状态的使用,哎,所以我们会发现啊,所谓的无状态的算子,我们说map是属于无状态的算子,事实上呢,我们也可以让它变得有状态。所以从这个角度上来讲,Link真正意义上实现了有状态的流失计算,它里边的每一步操作、每一个任务,每一个算子都可以认为是有状态的。这就是关于我们所说的状态分类,主要我们要了解的就是算子状态和按键分区状态operator state和k state,那这里还需要强调的一点就是不管是算子状态还是按键分区状态,他们对于每一个分区而言,状态的实力都是在本地维护的。所以不同分区之间。
09:05
他们的状态是没有办法相互交流的,如果是不同分区的数据的话,肯定就没有办法跨分区访问到别的状态。哎,当然了,后面我们还会提到一种比较特殊的情况,那就是所谓的广播状态,如果是广播状态的话,其实也不是跨分区去访问状态了,而是所有分区的状态都长得一样,他们都是同一份状态,这个时候我们就可以认为所有数据都访问到了相同的状态。这就是关于状态分类的基本概念。
我来说两句