00:00
接下来我们要介绍的这一章呢,是第九章,这张非常的重要。这里就涉及到了前面我们已经出现过无数次的一个重要概念,那就是状态。呃,其实前面我们都已经介绍过,对于flink来讲,它本身就是一个有状态的流逝计算大数据引擎,诶,那之前在介绍的过程当中,其实关于状态的应用我们也已经有所涉及了,像之前在很多代码里边,比如说我们在第八章的实时对账的代码,当时其实就自己定义的时候,我们用了两条做了一个connect进行连接啊,那么在处理的过程当中呢,就用到了value state,去保存当前已经到达的事件,把它保存了,保存成为当前处理的一个状态。那另外呢,在之前我们还有一个top n这样一个例子,里边也曾经用过状态,而且用的类型还不一样,我们当时是使用了一个列表状态,一个list state,把它定义成了一组列表,把当前收集到的所有数据啊,就是已经来到的这些数据收集到一个列表里边保存起来,后续再拿出来统一使用,当然还借用了定时器相关的一些内容。
01:22
那么前面我们都已经这样用过了。对于flink而言,状态。到底是什么?他在编程的过程当中又有没有一些特定的规律可循呢?啊,这就是这一章主要要解答的问题。所以对于flink而言,它里边的状态到底怎么定义,怎么样作为一个系统性的结构把它整个管理起来,然后在代码当中又怎么样去使用,怎么样用它编程去实现功能,这就是我们这一章的主要内容。所以我们要讲的其实是flink当中的状态管理机制以及状态编程的用法。
02:03
其实我们前面看到啊,不管是简单聚合,窗口聚合,还是处理函数应用的时候。除了我们前面已经明确提出来的,在代码当中定义出来的list之外,其实啊,对于窗口而言,前面我们提到过。窗口的简单聚合,那么其实里边它本身就是要有一个聚合状态,我们提到aggregate在做这个操作的时候。Aggregate function里边有三个不同的数据类型,那么中间的这个ACC其实就是当前窗口算子里边保持着的一个累加器,它其实就是一个中间的聚合状态啊,那当然了,呃,对于其他的一些算子,其实本质上都是一样的。除了窗口之外,像我们之前讲到的一般的reduce,一般化的这种简单聚合操作,其实里边也都是保持了一个状态,比如说我们说的这个workout,来一个数据就加一次,来一个数据就加一次,那当然当前计算的这个算子里边就有一个计数器作为状态保存下来。
03:17
所以对于弗link而言,状态其实是无处不在。那么对于flink这样的分布式系统来讲,我们会想到,诶,如果说我当前只有一个节点,一个任务的话,那很简单,诶,我当前这个数据来了之后,我把它对应的状态保存下来就可以了。但我们现在是一个分布式的结构,分布式的结构的话,那就会有一个问题,我是不是需要每一个分布式的算子任务都保存当前的状态呢?然后这些当前他们分布式的状态保存是否还需要进行互相的通信,最后再合并在一起,得到我们完整的处理结果呢?那另外还有就是假如说我们当前每一个任务都保存自己当前的状态的话。
04:05
如果说我们出现了故障,如果说之后我们从故障当中要去进行恢复,那怎么样能让它完整的恢复出之前所有的状态呢?啊,那自然想到我们应该把状态做一个持久化的保存。所以所有的这些事情都涉及到我们应该设计一整套的状态管理系统来对于状态进行统一的管理和保存。所以这张我们要处理的就是。状态flink里边状态到底是什么?状态的分类,怎么样去分,然后怎么样去用,以及怎么样去做保存,整个的状态管理的系统是什么样。那首先我们可以先来看一下flink里边状态的基本定义,前面我们提到过,在流处理里边,数据其实是连续不断的到来和处理,就是我们说的来一个就处理一个,来一个就处理一个,那在处理这个数据的过程当中呢?
05:06
可以跟其他的数据完全没有关系,就真的是。当前非常简单的,只基于当前的数据,哎,我把它转换,转换就得到了对应的一个输出,这就有点类似于之前我们所说的map操作。对于map操作而言,那当然它不依赖任何其他的数据了。但是我们回忆一下,对于之前work这样一个计算,我们用到了聚合操作的时候,你不是简单聚合好,还是使用者甚至是窗口聚合也好,那就说明每一个数据到来的时候,就不能直接基于当前数据的里边的内容得到我的输出了。我还要。结合一些之前的内容,所以结合之前其他数据的一些内容,这些数据就必须保存下来,作为我们当前算子任务的一部分,参与到后续的计算,那么这个保存下来的数据,这就是状态。
06:07
所以这里可以给出状态的明确定义,就是每个任务进行计算处理的时候,它可以基于当前的数据直接进行转换,类似于慢,也可以呢,依赖于其他的一些数据,比如说开窗口啊,比如说进行简单或者一般化的聚合。那么。这些由一个任务维护并且用来计算输出结果的需要依赖的其他的所有数据就都叫做当前这个任务的状态。那么接下来我们就会说,对于flink里边的算子而言,根据它到底有没有状态可以进行一个划分,那就是可以划分成有状态的算子和无状态的算子两种类型。当然了,无状态的算子我们说前面提到的这个像非常典型的map或者flat map,我们知道它其实跟map非常的类似,只不过就是有可能出现一对多的情况来,一条数据有可能输,输出多条有可能不输出,它更加灵活一些。
07:14
不管怎么样,他们都是不依赖其他的数据,只跟当前输入数据有关,所以他们正常情况下啊,这样的算子应用的时候就是一个无状态的算子,所以我们看到无状态算子只需要观察每个独立的事件,根据当前的输入数据直接转换,比如说啊,那我们说的这个map filter map,其实都是这样的一个过程。我们直接把一个圆。这样的数据转换成一个方形就可以了,它不依赖其他的数据。而与之对应。如果说我们在计算的过程当中,除了当前数据之外,还需要其他一些数据辅助计算的话,诶,那么需要的这些辅助数据那就必须要保存成状态了,所以如果说用到了状态这样的算子任务就叫做有状态的算子任务。
08:09
所以呃,最常见的状态呢,其实就是之前已经到达的数据,或者由之前已经到达的数据计算统计出的某个结果,比如说之前我们做这个呃,Workout,那当然就是应该有一个计数器了,就之前我不需要做别的计算,只需要记录到底来了几个数就可以。啊,那么对于像做这个求和计算sum的时候也非常简单,那就是把之前所有到达的数据做一个简单的求和就可以了,保存下来的那个当前的萨姆和就是当前算状态。啊,那比较特殊一点,或者比较复杂一点的情况,那其实就是窗口了,窗口算子里边会保存当前已经到达的所有数据,啊,这些其实也都是他的状态。那另外就是比如说我们需要检索到某种事件的模式,比如说呃,就像前面我们讲到的,类似于啊订单支付的这样一个行为检测,先有下单行为,然后再有支付行为,那么也应该把之前的一些行为保存下来,这些都是属于状态。
09:13
啊,那所以我们看到对于有状态的算子处理数据的流程,那就会比无状态算子要复杂一点,因为还涉及到了状态的读取和写入。整个的流程应该是首先有一个数据输入进来,来一个就处理一个,在处理的过程当中呢,还需要依赖一个辅助的数据,就是所谓的状态,所以还需要获取状态,第二步是获取状态。接下来呢,获取状态之后进行计算,在这个计算的过程当中,也需要去更新状态,最后得到我们想要的结果,把它发送出去,这就是有状态的算子基本的处理流程。啊,那这样的话,我们总结起来之前所有讲过的聚合算子。
10:02
包括窗口算子,窗口做的任务,各种各样的操作,我们知道窗口即使是不做聚合,即使你只是简单的把窗口里面的数据全部收集起来,最后到窗口出发的时候一下输出,那也需要有。一个保存所有数据的过程啊,那所以保存下来的所有数据就是当前窗口的状态,所以窗口算子都是有状态的算子。这就是我们对于有状态算子的一个基本的划分。这里需要强调的一点是,对于flink而言。其实它并没有划分的那么严格,为什么呢?呃,就是对于像map flat map这样,或者这样一些看起来无状态的算子。其实我们也可以用其他的方法去给它定义出状态。那那这其实就是我们之前所说的,在代码当中我是可以自定义状态,比如说我们之前提到的。
11:04
在top里边直接定义的一个列表状态啊,那或者说像我们在check里边直接定义的这样一个value,相当于就是一个只保存一个值这样的状态,之前我们所有做的操作都是在process function这个家族里边直接去定义的。但其实。我们会发现在这个状态进行操作的过程当中呢,它的关键点是要使用get contact运行上这个方法,然后去获取当前状态的一个控制句柄。所以理论上来讲,它不一定非得是在process function里边才能使用,只要能够调用get runtime contact运行上下文这个方法,那么就可以在里边获取状态距离,然后去定义状态,自己进行状态的操作和管理。那么什么样的函数里边,什么样的类里边可以去获取当前的运行上下文呢?前面我们也提到过,只要是。
12:06
只要是负函数类都可以,只要是function都可以啊,那对于这个。Process function,我们知道所有的process function,其实本质上啊,它都继承自抽象的函数类这样一个抽象类类型,所以我们可以认为process function都是负函数。所以当前我们对于。状态的使用,可以使用process去处理,去自定义状态,也可以直接用一个函数类就可以,比如说我们直接定义一个自定义的map function,比方说叫my map function my map。只要让他去。Extend。一个瑞什么方式。那么在后面就可以非常简单的。
13:03
用之前我们在process function里边类似的方式,可以去自定义状态,进行状态的操作。这就是我们所说的状态编程,而从这个意义上来讲,Flink里边的任何一个算子,因为我们说rich方这个函数类可以说是针对每一个算子都有对应的负函数类实现的啊,那么所以我们可以认为所有算子flink里边的所有算子都可以是有状态的算子。这也是为什么官网里边定义把flink叫做有状态的流式计算器啊,它所有的算子都可以定义出状态去进行操作。就是关于flink里边状态的基本定义,以及有状态算子的基本概念。
我来说两句