00:00
了解了状态的概念,那接下来我们再来看一下link当中的状态管理机制,这要从传统的数据处理讲起了,我们知道在传统的事物型处理架构里边啊,最典型的我们就把它想象成一个网站啊,非常典型的事务型处理,那就是来了一个请求发给了。后台的服务器啊,那么接收到这个请求之后,我们要去判断当前它要做的是什么,要调用相关的这个服务service,然后接下来可能我需要用到一些额外的数据,那这些额外的数据存放在哪里呢?比如说之前我们所说的work count来了一个单词,那很显然我要去查这个单词之前出现过多少次这个数据,对于我们大部分的这个外而言啊,肯定都是要把数据存放在一个数据库里的。或者是Mexico啊,或者是像go啊啊,或者是想要更快一点的话,我们可能用内存特征的这样一个red这样的一个集群去进行扩展,那不管是什么样的东西,它都是一个外部的数据存储,一个数据库,所以我们对于每一个请求的处理都需要对数据库进行读写操作。
01:19
那其实我们知道,对于实时的流处理而言,这样做那是需要频繁的读写外部数据库的。对于一个网站而言,可能我们能够同时处理的请求数量还是比较有限的啊,可能我们这个大部分的这个请求对于数据库的操作还是比较比较轻量级的啊,即使这样的话,我们知道在当前数据压力越来越大啊,需要的这个并并行度啊,并发量越来越高的时候,那其实对于数据库的扩展,数据库的分布式的这种读写要求也是越来越高的,那当然如果说我们能够以极快的速度和极稳定的性能把数据库进行扩展的话,进行读取的话。
02:08
这种方案也是没有问题,但是如果说数据规模扩大到非常大的时候,很显然就达不到我们的性能要求了,因为他总要跟外部数据库去发起请求,然后去进行写入,这一来一回肯定耗时间嘛。那我们自然就想了,怎么样能够在数据量非常大的时候把这个性能提升上来呢?那一个简单想法就是不要用外部数据库读写的方式。放在本地,那如果放在本地的话,最快的方案当然就是直接保存在内存当中了。所以在flink里边,它的解决方案非常简单粗暴,就是直接把状态保存在当前节点的内存里边,这样就可以最大程度的保证性。啊,那。另外我们就想到了,你既然是放在内存里边,这里有一个问题,如果说我们数据量足够大的话,那内存不也是要撑爆吗?呃,我们如果要是放在外部数据库的话,那数据库自然会帮我们解决这个扩展性的问题,如果你就一个节点的话,它这个内存放不下怎么办呢?啊,那自然这就有分布式的扩展了,我们说弗link本身是一个分布式的处理引擎,所以我们所有的数据来了之后,只要你把它的并行度设的足够大,给它分配足够多的slot资源,那么就会有足够多的并行任务,那每一个任务都会有它占据的这个slog资源里边都会有自己独立的一块内存资源。
03:43
接下来我们当然就在这一块内存资源里面,可以去存放自己相应的那些状态啊,那这样的话,你如果要是说当前这个内存放不下这么多状态,这么大的状态的话,那很简单,我再去扩展嘛,只要再扩展更多的节点,并行度再调大就可以,所以最终就变成了一个分布式进行方便扩展,然后来提高吞吐量这样一种解决方案。
04:10
啊,那对于这个flink而言,我们知道每一个算子任务都是可以去直接设置并行度的,然然后就可以在不同的lo上运行多个任务实例了,我们当时把这样的任务叫做并行的子任务,那么状态既然是在内存当中,其实我们就可以知道啊,就可以认为。当前的状态就是这个子任务实例里边的一个本地变量,接下来呢,当然就可以非常方便的被当前任务的业务逻辑代码去进行访问和修改。所以这么看来的话,好像这个状态管理非常简单啊,我们直接把它作为一个Java对象,然后交给。GVM不就可以了吗?GVM自动的可以。帮我们把它管理起来啊,另外还可以进行这个GC啊,可以去进行回收清理的功能。
05:06
但是前面我们说了,在大数据场景下,我们一个节点的内存显然是不够用的,我们需要有多个节点分布式的进行管理,但那这个时候他们之间彼此之间又怎么样去通信,怎么样去进行合并或者拆分这个状态的操作呢啊?而且另外还有就是说,在低延迟高吞吐的基础上,还要保证容错,也就是说假如说我处理的这个过程当中,某一个节点直接挂了,我现在需要去重启,重启的话,有可能当前的并行度就会发生改变,那这个时候状态怎么样去再去做拆分和重新分配呢?一系列复杂的问题就来了,对吧?所以这里边我们仔细的观察一下的话,仔细的分析一下,就会发现当前我们面对的问题主要是这两这三种。一个就是状态的访问权限啊,我们知道对于flink上聚合和窗口操作,它一般啊都是针对某个K而言的啊,就是先进行K得到一个k stream,然后接下来我们才去做这个对应的聚合和或者开窗进行操作。
06:16
我们知道这样的话,数据它其实是按照K的哈希值进行了分区的。聚合的结果呢,也只是针对当前的key有效。那对于这个K和之前我们所说的这个分区的关系,前面是说过的啊,KY按照当前K的哈希值把它进行一个分配。那当前每一个分区里边就只有一个K吗?这个是不确定的。因为我们当前的并行度可能有限,而这个K可能是非常非常多的啊,那正常情况下的话,一般。不同的K,它是有可能会分配到同一个分区任务上去。所以接下来我们就知道了,那对于同一个分区任务,假如说我们现在就是word count啊,那那不同的word都已经分配到同一个分区子任务上来了,如果说我们把当前它的那个count聚合起来的个数,那个计数器只作为当前的一个状态保存在这里的话,那岂不是我来了一个哈?
07:21
和来了一个word。当前状态都要加一吗?这样就乱了,这就不对了,哎,所以我们需要去考虑啊,我不能简单的把所有K对应的状态都保存成一个状态。那那当前我就应该应该还要按照不同的K把它进行一个划分了啊,所以我们应该有这样的一个管理机制,针对不同的K把状态要区分开啊。所以这个时候状态就不是简简单单的本地变量了,它还应该跟当前数据的K有关。那另外还有一个问题,就是前面我们提到的容错性,也就是故障恢复的时候,状态应该怎么样去变换呢?啊,那首先我们应该想到状态。
08:09
假如说想要能够去恢复的话,肯定需要先保存下来,那如果要保存下来,我们现在不是已经把它保存了吗?但是这只是保存在了内存里面。放在内存里边。好处是快。但是很显然它的缺点就是不稳定,如果出现故障,很显然内存里边所有的东西都都丢掉了,那如果丢掉了之后,前面我计算的结果怎么办呢?那就只能从头开始重放数据,重新算一遍。这对于我们实时流处理而言,那是几乎不可接受的,延迟就太高了吧,所以我们要做的就是需要把它进行持久化的保存,做一个备份,这样的话发生故障之后,可以从这个备份里边快速的把状态恢复出来。但是这个时候呢,又会涉及到另外一个问题,这第二个问题,容错性的问题,和后面这个横向扩展性的问题,其实是一个就是因为不管是发生故障之后重启,还是说我们当前啊,当前发现这个资源不够了,那资源不够怎么办呢?当然我应该把当前任务先暂停一下,然后把资源进行更多的分配,然后再重新启动,这样的话,整个这个。
09:25
资源就会更多,当前我资源的分配也可以更加平均的分配出去,进行横向的扩展,性能就提升了,哎,那当前我如果说出现这个故障,或者说需要重新的调增大并行度,然后分配更多资源的时候,都涉及到了。并行度的减小或者增大,那么并行度的增大减小的时候,我们当前的状态,之前这里边已经保存的状态。显然恢复之后也不能是按照原先的样子原封不动的去放了,因为我们知道,假如说你有一个节点直接挂掉的话,很显然恢复之后,从持久化的那个空间里边把状态拿出来,恢复之后,你就缺少了之前的这一部分节点的资源了,哎,那所以之前他的这一部分状态都应该平均分配到还能正常工作的其他节点上去。
10:24
这是并行度缩小的情况。那假如说是之前的这个节点本身。资源,内存资源已经不够了,已经填满了,现在呢,我们要扩展当前的并行度,那是又重新分配了一个新的节点过来并入调大,那这个时候假如说我们之前的这个状态还是原封不动的话,那很显然接下来的这这两个原先的这两个并行子任务,他就没有办法再继续扩展这个状态,没有办法处理新的数据。而新增加加的这一个节点呢,里边是空的,它没有之前的任何数据啊,那接下来相当于我就只能用新增的这一个节点分配出来的这个并行子任务去进行新数据的处理,这显然不是我们想要的,我们想要的是它全部都并行处理啊,那怎么办呢?很显然应该把之前的这个状态再打乱重组,都分出一部分来分给新增的这个并行责任。
11:23
这样的话,诶,大家平均分配,那每一个都不再是饱和的状态,就可以进一步的去处理新的数据,去把这个内存进行占用,然后去把这个状态进行扩展了,这就是我们想要的状态。那涉及到了这个并行度调整之后状态的重组,所以我们会发现啊,在分布式架构里边,想要做这样一个状态的管理,并不是那么一件非常简单的事情。好在弗link作为一个有状态的流式处理引擎,它已经在框架上帮我们把这一切全部搞定。在弗link里边呢,它是有一整套完整的状态管理机制的,它会把底层的一些核心功能全部都封装起来,包括状态的高效存储和访问,持久化的保存以及故障恢复,还有资源扩展,或者说减少的时候啊,并行度调整的时候,状态的重组调整,这些flink底层帮我们全部搞定了。
12:26
那这样的话,我们只需要调用相应的API就可以进行很方便的使用,或者对于整个应用的这个容错机制啊,就是故障恢复的时候,它的这一套机制进行一些配置,那这样的话,好处就在于我们作为一个大数据的程序员,就不需要去考虑架构上相关的这些状态管理的问题了,而把更多的精力可以放在业务逻辑的开发上。这就是整个flink的状态管理架构的思路解决方案。
我来说两句