目录
1.2.4 分布式快照算法(非Barrier对齐的精准一次)
1.3.4通用增量 checkpoint (changelog)
1.5.3.3 从savepoint恢复作业,同时修改状态后端
3.2.1.1预写日志(write-ahead-log,WAL)
3.2.1.2两阶段提交(two-phase-commit,2PC)
在Flink中,有一套完整的容错机制来保证故障后的恢复,其中最重要的就是检查点。
1、检查点
在流处理中,我们可以用存档读档的思路,将之前某个时间点的所有状态保存下来,这份“存档”就是所谓的“检查点”。
这里所谓的检查,是针对故障恢复结果而言的:故障恢复之后继续处理的结果,应该与发生故障之前完全一致,我们需要检查结果的正确性,所以有时又会把checkpoint叫做“一致性检查点”。
1.1 检查点的保存
1.1.1 周期性的触发保存
“随时存档”确实恢复起来方便,可是需要我们不停地做存档操作。如果每处理一条数据就进行检查点的保存,当大量数据同时到来时,就会耗费很多资源来频繁做检查点,数据处理的速度就会受到影响。所以在Flink中,检查点的保存是周期性触发的,间隔时间可以进行设置。
1.1.2 保存的时间点
我们应该在所有任务(算子)都恰好处理完一个相同的输入数据的时候,将它们的状态保存下来。这样做可以实现一个数据被所有任务(算子)完整地处理完,状态得到了保存。
如果出现故障,我们恢复到之前保存的状态,故障时正在处理的所有数据都需要重新处理;我们只需要让源(source)任务向数据源重新提交偏移量、请求重放数据就可以了。当然这需要源任务可以把偏移量作为算子状态保存下来,而且外部数据源能够重置偏移量;kafka就是满足这些要求的一个最好的例子。
1.1.3 时间点的保存与恢复
1.1.3.1保存
当我们需要保存检查点时,就是在所有任务处理完同一条数据后,对状态做一个快照保存下来。
故障异常:
1.1.3.2 恢复的具体步骤:
- 遇到故障之后,第一步重启,重启后,所有任务的状态会清空
- 读取检查点,重置状态
- 找到最近一次保存的检查点,从中读取每个算子任务状态的快照
- 重置偏移量
- 为了保证不丢数据,我们应该从保存检查点后开始重新读取数据,可以通过source任务向外部数据源重新提交偏移量(offset)来实现
1.2 检查点算法
在Flink中,采用了基于Chandy-Lamport算法的分布式快照,可以在不暂停整体流处理的前提下,将状态备份保存到检查点。
1.2.1 检查点分界线(Barrier)
借鉴水位线的设计,在数据流中插入一个特殊的数据结构,专门用来表示触发检查点保存的时间点。收到保存检查点的指令后,Source任务可以在当前数据流中插入这个结构;之后的所有任务只要遇到它就开始对状态做持久化快照保存。由于数据流是保持顺序依次处理的,因此遇到这个标识就代表之前的数据都处理完了,可以保存一个检查点;而在它之后的数据,引起的状态改变就不会体现在这个检查点中,而需要保存到下一个检查点。
这种特殊的数据形式,把一条流上的数据按照不同的检查点分隔开,所以就叫做检查点的“分界线”(Checkpoint Barrier)。
1.2.2 分布式快照算法(Barrier对齐的精准一次)
watermark指示的是“之前的数据全部到齐了”,而barrier指示的是“之前所有数据的状态更改保存入当前检查点”:它们都是一个“截止时间”的标志。所以在处理多个分区的传递时,也要以是否还会有数据到来作为一个判断标准。
具体实现上,Flink使用了Chandy-Lamport算法的一种变体,被称为“异步分界线快照”算法。算法的核心就是两个原则:
- 当上游任务向多个并行下游任务发送barrier时,需要广播出去;
- 而当多个上游任务向同一个下游任务传递分界线时,需要在下游任务执行“分界线对齐”操作,也就是需要等到所有并行分区的barrier都到齐,才可以开始状态的保存。
- 场景举例:
设置算子并行度为2。
设置2个source任务,会分别读取2个数据流(或者一个源的不同分区)。
检查点保存算法的具体过程:
(1)触发检查点:JobManager向Source发送Barrier;
(2)Barrier发送:向下游广播发送;
(3)Barrier对齐:下游需要收到上游所有并行度传递过来的Barrier才做自身状态的保存;
(4)状态保存:有状态的算子将状态保存至持久化。
(5)先处理缓存数据,然后正常继续处理
(补充)由于分界线对齐要求先到达的分区做缓存等待,一定程度上会影响处理的速度;当出现背压时,下游任务会堆积大量的缓冲数据,检查点可能需要很久才可以保存完毕。
为了应对这种场景,Barrier对齐中提供了至少一次语义以及Flink 1.11之后提供了不对齐的检查点保存方式,可以将未处理的缓冲数据也保存进检查点。这样,当我们遇到一个分区barrier时就不需等待对齐,而是可以直接启动状态的保存了。
1.2.3 分布式快照算法(Barrier对齐的至少一次)
- JobManager发送指令,触发检查点保存;source任务中插入一条分界线,并将偏移量保存到远程的持久化存储中。
- 状态快照保存完成,分界线向下游传递
状态存入持久化存储之后,会返回通知给Source任务;Source任务就会向JobManager确认检查点完成,然后跟数据一样把分界线向下游任务传递。
说明:由于Source和Map之间是一对一(forward)的传输关系(这里没有考虑算子链),所以barrier可以直接传递给对应的Map任务之后Source任务就可以继续读取新的数据了。与此同时,SumI已经将第二条流传来的(hello,1)处理完毕,更新了状态
- 向下游多个并行子任务广播分界线,执行分界线对齐
Map任务没有状态,所以直接将barrier?继续向下游传递。这时由于进行了keyBy分区,所以需要将barrier)广播到下游并行的两个Sum
任务。同时,Sum任务可能收到来自上游两个并行Map任务的barrier,所以需要执行“分界线对齐”操作。
而Sum1只收到了来自Map2的barrier,所以这时需要等待分界线对齐。而如果分界线己经到达的分区任务Map2又传来数据,直接计算等到下一个Barrier到达时做状态的保存。重新启动时介于两个Barrier.之间分界线己经到达的分区任务Map2传过来的数据会再次计算(至少一次)。
- 分界线对齐后,保存状态到持久化存储
各个分区的分界线都对齐后,就可以对当前状态做快照,保存到持久化存储了。存储完成之后,同样将barrierl向下游继续传递,并通知JobManager保存完毕。
这个过程中,每个任务保存自己的状态都是相对独立的,互不影响。我们可以看到,当Sum将当前状态保存完毕时,Source1任务
已经读取到第一条流的第五个数据了。
1.2.4 分布式快照算法(非Barrier对齐的精准一次)
- JobManager发送指令,触发检查点的保存;Source任务中插入一个分界线,并将偏移量保存到远程的持久化存储中。
说明:并行的Source任务保存的状态为3和1,表示当前的1号检查点应该包含:第一条流中截至第三个数据、第二条流中截至第一个数据的所有状态更改。可以发现Source任务做这些的时候并不影响后面任务的处理,Sum2已经处理完了第一条流中传来(world,l)对应的状态也有了更改。
-
状态快照保存完成,分界线向下游传递
状态存入持久化存储之后,会返回通知给Source任务;Source任务就会向JobManager确认检查点完成,然后跟数据一样把分界线向下游任务传递。
说明:由于Source和Map之间是一对一(forward)的传输关系(这里没有考虑算子链),所以barrier可以直接传递给对应的Map任务。之后Source任务就可以继续读取新的数据了。与此同时,Sum1已经将第二条流传来的(hello,1)处理完毕并更新了状态。
-
向下游多个并行子任务广播分界线,执行非Barrier对齐
Map任务没有状态,所以直接将barrier继续向下游传递。这时由于进行了keyBy分区,所以需要将barrier广播到下游并行的两个Sum任务。同时,Sum任务可能收到来自上游两个并行Map任务的barrier,执行“非Barrier对齐”操作。
这里我们我只关注Sum1的细节,Sum1在第一个barrier到达时就开始执行非对齐检查点。
-
向下游多个并行子任务广播分界线,执行非Barrier对齐
核心思想:只要in-fight的数据也存到状态里,barrier就可以越过所有in-flight的数据继续往下游传递。
此时的Sum1任务在第一个Barrier到达输入缓冲区时:
①直接将barrier放到输出缓冲区末端,向下游传递。
②标记数据(图中标颜色部分)
一是被第一个barrier越过的输入缓冲区和输出缓冲区的数据
二是在其他barrier之前的所有数据
③把标记数据和状态一起保存到checkpoint中,从checkpoint恢复时这些数据也会一起恢复到对应位置
1.3检查点配置
检查点的作用是为了故障恢复,我们不能因为保存检查点占据了大量时间、导致数据处理性能明显降低。为了兼顾容错性和处理性能,我们可以在代码中对检查点进行各种配置。