Flink容错机制(一)

目录

十:容错机制

10.1 检查点(Checkpoint)

10.1.1 检查点的保存

10.1.2 从检查点恢复状态

 10.1.3 检查点算法

 10.1.4 检查点配置

10.1.5 保存点(Savepoint)


十:容错机制

流式数据连续不断地到来,无休无止;所以流处理程序也是持续运行的,并没有一个明确 的结束退出时间。机器运行程序,996 起来当然比人要容易得多,不过希望“永远运行”也是 不切实际的。因为各种硬件软件的原因,运行一段时间后程序可能异常退出、机器可能宕机, 如果我们只依赖一台机器来运行,就会使得任务的处理被迫中断。

一个解决方案就是多台机器组成集群,以“分布式架构”来运行程序。这样不仅扩展了系 统的并行处理能力,而且可以解决单点故障的问题,从而大大提高系统的稳定性和可用性。

在分布式架构中,当某个节点出现故障,其他节点基本不受影响。这时只需要重启应用, 恢复之前某个时间点的状态继续处理就可以了。这一切看似简单,可是在实时流处理中,我们 不仅需要保证故障后能够重启继续运行,还要保证结果的正确性、故障恢复的速度、对处理性 能的影响,这就需要在架构上做出更加精巧的设计。

在 Flink 中,有一套完整的容错机制(fault tolerance)来保证故障后的恢复,其中最重要 的就是检查点(checkpoint)。在第九章中,我们已经介绍过检查点的基本概念和用途,接下来 我们就深入探讨一下检查点的原理和 Flink 的容错机制。

10.1 检查点(Checkpoint)

发生故障之后怎么办?最简单的想法当然是重启机器、重启应用。由于是分布式的集群, 即使一个节点无法恢复,也不会影响应用的重启执行。这里的问题在于,流处理应用中的任务 都是有状态的,而为了快速访问这些状态一般会直接放在堆内存里;现在重启应用,内存中的 状态已经丢失,就意味着之前的计算全部白费了,需要从头来过。就像编写文档或是玩 RPG游戏,因为宕机没保存而要重来一遍是一件令人崩溃的事情;这种惨痛的经历让我们养成了一 个好习惯——随时存档,这样即使遇到宕机也可以读档继续了。

在流处理中,我们同样可以用存档读档的思路,把之前的计算结果做个保存,这样重启之 后就可以继续处理新数据、而不需要重新计算了。进一步地,我们知道在有状态的流处理中, 任务继续处理新数据,并不需要“之前的计算结果”,而是需要任务“之前的状态”。所以我们 最终的选择,就是将之前某个时间点所有的状态保存下来,这份“存档”就是所谓的“检查点” (checkpoint)。

遇到故障重启的时候,我们可以从检查点中“读档”,恢复出之前的状态,这样就可以回 到当时保存的一刻接着处理数据了。

检查点是 Flink 容错机制的核心。这里所谓的“检查”,其实是针对故障恢复的结果而言 的:故障恢复之后继续处理的结果,应该与发生故障前完全一致,我们需要“检查”结果的正 确性。所以,有时又会把 checkpoint 叫作“一致性检查点”。

10.1.1 检查点的保存

1. 周期性的触发保存

随时存档”确实恢复起来方便,可是需要我们不停地做存档操作。如果每处理一条数据 就进行检查点的保存,当大量数据同时到来时,就会耗费很多资源来频繁做检查点,数据处理 的速度就会受到影响。

所以更好的方式是,每隔一段时间去做一次存档,这样既不会影响数据 的正常处理,也不会有太大的延迟——毕竟故障恢复的情况不是随时发生的。在 Flink 中,检查点的保存是周期性触发的,间隔时间可以进行设置。

所以检查点作为应用状态的一份“存档”,其实就是所有任务状态在同一时间点的一个“快照”(snapshot),它的触发是周期性的。具体来说,当每隔一段时间检查点保存操作被触发时, 就把每个任务当前的状态复制一份,按照一定的逻辑结构放在一起持久化保存起来,就构成了检查点。

2. 保存的时间点

这里有一个关键问题:当检查点的保存被触发时,任务有可能正在处理某个数据,这时该怎么办呢?

方案:(如果一个数据被所有任务处理完,则保存状态,否则,恢复到之前保存的状态,然后故障恢复后,重新提交任务)

当所有任务都恰好处理完一个相同的输入数据的时候,将它们的状态保存下来。首先,这样避免了除状态之外其他额外信息的存储,提高了检查点保存的效率。 其次,一个数据要么就是被所有任务完整地处理完,状态得到了保存;要么就是没处理完,状态全部没保存:这就相当于构建了一个“事务”(transaction)。如果出现故障,我们恢复到之前保存的状态,故障时正在处理的所有数据都需要重新处理;所以我们只需要让源(source) 任务向数据源重新提交偏移量、请求重放数据就可以了。这需要源任务可以把偏移量作为算子状态保存下来,而且外部数据源能够重置偏移量;Kafka 就是满足这些要求的一个最好的例子。

3. 保存的具体流程  

检查点的保存,最关键的就是要等所有任务将“同一个数据”处理完毕。下面我们通过一 个具体的例子,来详细描述一下检查点具体的保存过程。

回忆一下我们最初实现的统计词频的程序——WordCount。这里为了方便,我们直接从数 据源读入已经分开的一个个单词,例如这里输入的就是:

“hello”“world”“hello”“flink”“hello”“world”“hello”“flink”……

对应的代码就可以简化为:

SingleOutputStreamOperator<Tuple2<String, Long>> wordCountStream = 
env.addSource(...) 
 .map(word -> Tuple2.of(word, 1L)) 
 .returns(Types.TUPLE(Types.STRING, Types.LONG)); 
 .keyBy(t -> t.f0); 
 .sum(1); 

源(Source)任务从外部数据源读取数据,并记录当前的偏移量,作为算子状态(Operator State)保存下来。然后将数据发给下游的 Map 任务,它会将一个单词转换成(word, count)二元 组,初始 count 都是 1,也就是(“hello”, 1)、(“world”, 1)这样的形式;这是一个无状态的算子任 务。进而以 word 作为键(key)进行分区,调用.sum()方法就可以对 count 值进行求和统计了;Sum 算子会把当前求和的结果作为按键分区状态(Keyed State)保存下来。最后得到的就是当 前单词的频次统计(word, count)

当我们需要保存检查点(checkpoint)时,就是在所有任务处理完同一条数据后对状态做个快照保存下来。例如上图中,已经处理了 3 条数据:“hello”“world”“hello”,所以我们 会看到 Source 算子的偏移量为 3;后面的 Sum 算子处理完第三条数据“hello”之后,此时已 经有 2 个“hello”和 1 个“world”,所以对应的状态为“hello”-> 2,“world”-> 1(这里 KeyedState底层会以 key-value 形式存储)。

此时所有任务都已经处理完了前三个数据,所以我们可以把当前的状态保存成一个检查点,写入外部存储中。

至于具体保存到哪里,这是由状态后端的配置项 “ 检查点存储 ”( CheckpointStorage )来决定的,可以有作业管理器的堆内存 (JobManagerCheckpointStorage)和文件系统(FileSystemCheckpointStorage)两种选择。一般 情况下,我们会将检查点写入持久化的分布式文件系统

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值