00:00
那有没有更加一般化,更加通用的,保证精确一次状态一致性的这样的方式呢?哎,那有的,就是第二种方式,事物写入。啊,其实我们能想到啊,输入端最大的一个问题,其实就是因为之前假如说已经数据来了之后,已经做过写入的话,哎,那就写都已经写入了,那已经收不回来了嘛,啊那接下来如果说数据又发生了故障,发生了回滚去重放数据的时候,那当然就会出现重复写入,这个我们没办法解决。那这里我们可以考虑的就是说,那能不能撤回一条我们之前已经写入的数据呢,就像微信里边我们撤回一条消息一样,做一个回滚呢?哎,那我们自然就想到了利用事物的特性就可以做到这样一点。啊,那我们都知道事物它本身啊,是一系列操作流程的一个包装啊,一系列的一个组合,那他们如果在操作的过程当中呢,相当于就是要么都发生,要么都不发生啊,就是如果要是发生中间啊,发生故障的话,就全部撤回了,所以我们说这个事物有四个基本特性吧,就是acid啊,原子性,一致性,隔离性和持久性。
01:14
诶,那简单最简单的一个例子,我们说就是像这个银行转账,那银行转账在底层的数据库里边,我们知道啊,其实非常简单,那就是。A给B要去做一个转账。那就是A的这个账户里边。减一个值B的这个账户里边加一个值就完了嘛,哎,这就是一个转账的过程,那这里面涉及到的一减一加两个操作,那我们就会发现啊,那假如说。他们俩不是完全同步的,如果是A先在里边减了一个值,然后这个时候发生故障了。诶,那这个时候怎么办呢?假如说我们发生故障之后,直接恢复出来,就是减了这个值之后的状态的话,那就会出现这笔转账,A那边已经转出去了,A已经扣款了,但是B没收到,凭空就少了这么多钱,这当然是不行的,哎,所以我们就应该把这两个操作包装成一个事物。
02:08
要不都发生,如果中间发生故障的话,诶,那就A之前扣的这个钱也要给他再补回来,相当于他就没有发生这一步转账,这个转账就撤销掉了。这就是我们所说的这个事物,那对应在这个flink流处理的过程写入到外部系统的时候呢,我们构建的事物就应该对应着向外部系统的写入操作。啊,那我们说这个事物,它主要就是一组一批操作,我们应该把它捆绑在一起,要么都发生,要么都不发生,那当前我们这个像外部系统的写入啊,到底是要做一个什么样的置物型的包装呢?啊,那这里边我们简单的想法,当然就是说要对应着检查点的保存,哎,我们知道啊,来了这个Barry之后,做了一个检查点的保存。
03:00
前面这个叫做一号检查点,后面这个叫做二号检查点,那我们知道啊,在一号和二号。之间到来的所有数据,如果中间发生了故障的话,那就相当于这些数据的处理都没有保存到检查点里面啊,因为我们在中间过程当中就故障了嘛,二号检查点肯定还没保存啊,那所以要恢复的话,只能恢复一号检查点里边的状态,那这个时候它后边的所有数据都要重放,所以我们就应该把什么呢?就是把两个检查点之间所保存的所处理的所有数据对于处理结果向外部系统的写入。包装成一个式。哎,也就是说所有这些数据啊,他要不就一批都写入到外部系统当中,要不呢,诶就都不写入,都回滚,诶那这个对应的checkpoint啊,因为我们当前做这个检查点保存之后啊,如果说中间发生故障的话,我们应该把他们的这个事物直接撤销掉,接下来恢复的是之前的这个一号检查点,诶那所以他们的状态当然就都没有处理了,他们对应的数据呢,也就都被撤销,都没有写入到外部系统,那假如说我们当前这个数据都已经正常处理完了,而且保存了二号检查点的话,诶,那当然接下来如果后边再发生故障的话,我们就可以恢复到二号检查点对应的状态,那之前所有的数据当然就都已经处理完了,都已经处理完了,当然就可以提交这个事物了啊,就相当于所有的这个数据啊,真正意义上的写入到外部系统当中可用。
04:40
这就是我们进行事物写入的一个基本的思路,那具体的实现上的话啊,我们这里边又会介绍两种基本的用法啊,那一种就叫做预写日志wal,另外一种叫做两阶段提交to PC,这两个缩写其实都非常的有名啊,啊首先我们来看这个预写日志。
05:01
那预写日志这种方式其实是非常简单的啊,那整体来说呢,它其实就是每个数据我们在做这个流处理的时候,那应该是来一条啊think任务这里。就应该把对应的这个处理结果应该要直接写入到外部系统了,那现在呢,你不要着急写入,我们不是说你要等所有的数据啊,就是已经在那个checkpoint对应的那个检查点里边已经状态保存了之后,我才把所有的这个数据才真正意义上写入吗?哎,那所以我干脆啊,前面我就不要写入,直接在这里。把它做一个缓存。缓冲起来,那所有这些缓冲的数据呢,就可以作为think这个算子任务它对应的状态,那如果说当前我们遇到这个checkpoint的分界线啊,Barrier think任务要做当前的快照保存的时候,就把所有的这些数据缓存存的数据也作为状态快照保存起来。
06:01
然后接下来什么时候真正把它一批写入到外部呢?诶,那就是等到job manager已经确认当前的检查点已经保存完成的时候。这个时候我确定啊,当前如果要发生故障,回滚就可以回滚到这个状态了啊,那这个时候呢,我再把所有缓冲区里面的数据一批写到外部系统里面。啊,那这个过程很明显就像一个批处理一样啊,哎,所以理论上来讲这个非常的简单,我们提前做一个缓存就可以,所以无论什么样的外部存储都可以用这种批处理的方式,一批把它写入直接搞定啊,那在底层呢,弗link给我们提供了一个模板类,就叫做generic write ahead think啊,那这W本来就是write ahead log预写日志这样一个缩写,那所以这样一个模板类呢,就可以实现预写日志这种事物型的写入方式啊,那当然了,预写日志这种方式呢,也有它的问题啊,啊就首先我们会想到这个过程当中就相当于批处理了嘛,最后写入的这一步操作,它的延迟就会比较大啊,那另外呢,就是说我们会发现他的这个一批写的方式呢,有可能会写入失败,所以最后的这一次写入的这个过程啊,他还得有一个。
07:22
最终写入成功的确认返回所所以我们会发现啊,就是前面我们说它是已经接到了job manager返回checkpoint的检查点,保存完成信息的时候才去做一批的写入,但是你最后这个写入呢,又有可能失败,你还得等他的确认返回好,那假如说我们当前这个最终写入结果给失败了。没写入进去,而我们前面的checkpoint已经完成了,那之后如果发生故障回滚的时候,它是会回滚到这个状态,但是我们这个数据呢,又没真的写进去,这其实是会出现问题的啊啊,那当然了,这里面我们可以用一些其他额外的方式去进行保证。
08:04
但是这个过程就还是会比较麻烦啊,这是关于这个预写日志啊,那我们会发现这个也不是特别的完美,那最完美的事物型醒悟的方式是什么呢?那就是后边我们最后要讲的啊,终极BOSS2阶段提交two PC。To PC这种方式呢,顾名思义,它就是把我们整个的提交最终啊,像外部系统写入的这个提交分成了两个阶段,首先呢,先做一个预提交啊,预提交我们可以认为就是相当于在这个流处理处理的过程当中啊,Think任务来了,一个数据处理的最终的结果,我们就直接写入到外部系统了,但是注意这个写入外部系统只是预提交。也就是说数据写是写进去了,但是呢,还不是真正可用的状态,它是相当于构建了一个事物,在事物当中提交进去的,也就是说这个事物。
09:00
没有正式提交之前,这个是有可能被撤销的啊,那什么时候正式提交呢?诶,当然就还是要跟检查点checkpoint要对应起来,当我们当前的这个checkpoint已经保存完成成功的时候,装发出这个信息的时候,那就把对应的这个事物直接做一个提交,那所有预提交的这个数据就正式可用了,这个阶段就叫做正式提交。所以我们看到这个过程呢,它是真正意义上的构建了一个事物,所以它对于外部系统是有要求的,外部系统必须要支持事物才可以。啊,那我们可以总结一下它具体的这个实现步骤啊,啊,那首先当前我们的第一条数据到来的时候,或者注意是收到某个检查点的分界线的时候,也就是说。什么时候启动当前的一个事物呢?那就是第一个数据来的时候啊,第一次去处理。
10:01
这个数据的时候啊,Think任务。处理第一个数据的时候,或者是接收到了下一个检查点,需要去开始保存这个当前任务的时候,那当然了,接下来所收到的数据就是另外一个事物了,我们的事物就是跟检查点一一对应,按照检查点分界线去进行分割的。所以在这种时候,我们启动一个新的事物,比方说第一个事物,我们就叫做TRANSACTION1。然后接下来呢,接收到的所有数据,那都是以这个事物去做预写入到外部存储系统的。也就是说数据是写进去了,但是呢,还没正式提交,有可能会被撤销掉,所以这是一个预提交的阶段。然后接下来什么时候正式提交呢?哎,那就是要等到,诶,那我们可能想到,那就是等到下一个checkpoint对应的那个分界线来嘛,注意不是下一个分界线,比方说这是一号拆放的,这是二号对应的那个Barry,下一个bar瑞尔来的时候呢,它对应的是。
11:06
创建一个新的transaction新的事物,这个叫做transaction to。那上边的这个transaction one啊,第一个事物什么时候真正提交呢?那是要等到一号checkpoint。真正保存结束的时候,这个标志是做manager向所有任务发出检查点保存完成的确认消息,这个时候我们才把对应的事物做一个正式提交好,那之前提交的所有数据就变成了真正可用的正式提交的数据了。哎,所以我们会发现啊,整个这个to pc2阶段提交它的处理过程呢,其实就是用一个事物开启一个事物,搭载了弗link本身处理这个检查点的机制啊,他就是按照这个处理检查点啊。开启一个检查点要进行保存的时候,我们就开启一个事物,那当前这个检查点真正保存结束的时候,就关闭对应的事物,一号检查点就开启一号事物,二号检查点就开启二号事啊,那同样对应的这个关闭的时候,也是一号检查点保存结束的时候,就关闭一号事物。
12:17
所以整个的处理流程呢,还是有处理。来一个数据就处理一个数据啊,对应的这个数据其实该写的都已经写进去了,并不是像之前预写日志攒一批才写入的啊,那所以这个过程就会快很多了,那最终它正式提交的时候呢,其实只是提交一个这个事物正式提交啊,关闭事务这样一个确认消息就可以了,所以整体来讲对于我们系统性能的影响是非常非常少的,性能可以说达到了最优。Link底层呢,哎,同样也给我们提供了这样的一个接口,这个接口名字就叫做to face commit think function2阶段提交啊,To PC嘛,To face commit,那这样的话,只要我们实现这样一个接口,然后实现里边对应的那些抽象方法啊,就可以提供真正的端到端的exactly ones保证。
13:10
所以我们会发现啊,这种两阶段提交当然是最好的一种方式了,但是它也有它的缺陷,就是它的要求太高了,就是对于外部系统有非常高的要求,首先必须提供事物支持,然后呢,呃,在两个检查点的间隔期之内,还能够开启一个事物,然后接受数据的写入,这个是预提交的阶段。啊,那么事物的正式提交,或者说我们整个这个数据的正式写入,那是要等到对应检查点完成,正式完成收到状编制发出的完成通知的时候才可以正式提交,那在这之前呢,整个事物必须是等待提交这样一个状态,预提交的状态。那如果说发生了故障恢复,或者说出现了一些其他情况的时候呢,这个等待的时间可能很长,哎,那假如说外部系统啊,我们这个事物也是有一个超时时间的嘛,外部系统这个时候把事物直接关闭了,他认为这个没有正常提交,而flink内部还在继续等待,还在继续去做这个检查点的保存的话,诶,那这种情况下不匹配就有可能导致未提交的数据发生丢失的情况。
14:21
啊,那另外还有就是说think任务啊,在当前的作业,如果说失败之后,还能够把这个事物也要能恢复出来,这也是一部分。另外提交事物也必须是密等操作,因为你发生这个故障之后,恢复出了事物,重新做一个提交的话,那必须对之前的结果是没有影响的,就重复提交一个事物应该是无效的。所以我们会发现啊,这个两阶段提交,尽管它非常的强大,而且这个对性能影响也很小,但是在实际应用的过程当中,同样会受到比较大的限制啊,那所以最终在实际项目应用过程当中啊,最终还是要去考量多方面的权衡啊,就是我们的外部系统到底支持不支持,另外呢,还得考虑能够达到的状态一致性的级别,就是最终的结果的正确性,以及处理的性能时间的延迟到底有多大。
15:13
这就是关于端到端的状态一致性的介绍。
我来说两句