Flink checkpoint实现算法的理论基础:Lightweight Asynchronous Snapshots for Distributed Dataflows

摘要:

分布式有状态的流式处理,让我们可以在云上部署和执行大规模持续计算,并实现了低延迟和高吞吐的目标。这种模式最基本的挑战之一是当发生了潜在故障,系统依旧提供正确的处理保证。当前的方法都依赖于周期性的全局快照,在故障时恢复数据。这些方法主要有两个缺点。第一,这些方法经常让整体计算停顿,影响数据摄入。第二,这些方法渴望于保存操作状态变化的所有记录。这种行为会导致产生超大量的快照信息,而这些快照信息并不是必须的。

本文提出了一种Asynchronous Barrier Snapshotting(ABS)方法。ABS是一种适合于主流数据流处理引擎的轻量级算法,且算法占用了极小的空间。ABS算法在无环的执行拓扑结构中,只保存了操作状态;而在有环的数据流中,保存了很小的记录日志。Apache Flink是一个分布式数据分析引擎,并支持有状态的数据流处理。我们在其上实现了ABS算法。在我们评估中显示,ABS算法没有对执行效率产生很大的影响,保持了先行扩展能力以及在频繁的快照操作时依旧能够良好的运行。

关键字: 容错,分布式计算,流处理,数据流,云计算,状态管理

1 引言

分布式数据流处理对于数据密集型计算来说是一种新兴的范式。这种范式需要能满足对于大量数据的持续计算,并能够实现高吞吐低延时的目标。时序性要求很强的应用,尤其是实时性分析领域的应用,可以充分利用流计算系统的特性。这种流计算系统有Apache Flink,Naiad等等。由于在真实的使用场景中错误是不能容忍的,所以容错是这些系统的核心问题。目前已知能够满足有状态的流计算系统的exactly-once语义的方法依赖于对于执行状态的全局持续快照。但是这样做有两个缺点影响实时流计算的时效性。同步快照技术让分布式计算的全体节点都停止运行,从而在全局获得了一个一致的状态。进一步说,就我们所知目前所有的分布式快照算法,都是通过通道或未处理消息的方式贯穿于执行图中,作为快照状态的一部分。通常情况下,这些数据比需要的大很多。

在本文的算法中,我们致力于在不影响性能的条件下,提供轻量级快照方案,尤其适合分布式有状态的数据流系统。我们的方案提供了异步状态快照,占用更小的空间,只保存了非循环执行拓扑操作状态。另外我们也考虑了循环执行拓扑的情况。通过使用拓扑图选中部分下行备份,从而保持快照状态最小化。我们的技术并没有让流操作挂起,且只占用了很小一部分的运行时开销。本文的主要成果可以概括成以下几点:

  • 我们提出并实现了一个异步快照算法,能够在无环图执行图中使用很小的快照。
  • 我们描述并实现了在有环执行图的情况下的算法。
  • 我们展示了算法在Apache Flink Streaming的应用和对比。

本文余下的部分组织结构如下:第二部分概述了当前在有状态数据流系统中的分布式全局快照算法。第三部分提供了Apache Flink处理模型的概览。第四部分详细描述了全局快照算法的主要方法。在第五部分,我们描述了恢复机制。最后,在第六部分对我们的工作进行了总结,第七部分是评估信息以及第八部分的展望和结论。

2 相关工作

在过去的数十年里,人们提出了若干个持续计算的恢复机制。将持续计算仿真成无状态的分布式批处理系统依赖于状态重算,例如DiscretizedStreams和Comet。另外,有状态的流计算系统使用检查点来保存全局的一致快照,从而实现错误恢复,例如Naiad,SDGs,Piccolo和SEEP。

分布式环境的全局一致性快照的问题已经在过去的几十年内被广泛的研究分析,例如Chandy和Lamport。理论上来说一个全局快照反映了一次执行的所有状态,或者是一个特定实例的一次操作状态。Naiad提供了一个简单但是代价很高的方法。这个方法通过三步创建一个同步快照:首先将执行图上所有的计算挂起,然后进行快照,最后在全局快照结束的时候引导所有的任务继续执行他的操作。这个方法对于吞吐量和空间的要求都很高,因为需要阻塞整体的计算,同时依赖于上行备份在生产者端的记录。另外一个由Chandy和Lamport提出的流行方法在许多的系统中应用。系统在进行上行备份的时候执行异步快照。这种方法通过在执行图插入分布式标签,从而触发算子和通道状态的持久化。这个方法的上行备份同样需要占用大量的空间,且备份记录的计算会导致更高的恢复时间。我们的方法扩展了Chandy和Lamport的异步快照思想,同时对于无环图不会有备份日志记录,对于有环图保存可选择的备份记录。

3 背景:Apache Flink

我们当前的工作以Apache Flink Streaming的容错机制所导向。Apache Flink Streaming是Apache Flink Stack的一部分。Apache Flink系统框架提供批处理Job和流计算Job统一处理框架。每个Job都是由多个有状态且互相通信的task组成。Flink中的分析工作被编译成task的有向图。Job从系统外部获取数据,并以流水线的方式在task图中流转。task将根据接收到的输入数据持续计算内部状态,并产生新的输出数据。

3.1 流式计算模型

用于流处理的Apache Flink API允许复杂流分析Job的组合,并使用公开无界分区数据流作为它的核心数据。这些被抽象成DataStream对象。DataStream可以通过外部数据源创建,或者由其他的DataStream唤起。DataStream支持若干个算子(operator),例如map,filter以及reduce。 它们以高阶函数的形式应用于每条记录并生成新的数据流。每个算子可以通过放到并行的实例上运行,分别负责自己对应的数据流,从而实现算法的并行。这就实现了分布式流计算。

示例1的代码展示了怎样用Apache Flink实现一个简单的累加计算单词数目的方法。在这个程序中,我们从一个text文件中读取单词,并在标准输出打印每个单词的个数统计。在程序中,需要记录文件的读取位置以及内部计数器需要记录单词的个数。这些表明这是一个有状态的流计算程序。
在这里插入图片描述

1 val env : StreamExecutionEnvironment = ...
2 env.setParallelism(2)
3
4 val wordStream = env.readTextFile(path)
5 val countStream = wordStream.groupBy(_).count
6 countStream.print

3.2 分布式数据流的执行

当一个用户执行一个应用,DataStream的所有算子都被编译到一个执行图中。类似于Naiad的概念,执行图是一个有向图G=(T,E)。端点T代表所有的任务,边E代表人物之间的通道。图1展示了上面示例的执行图。如图所示,每个算子的示例都封装到对应task中。当没有输入源,则可以将task看做数据源;当没有输出通道,则可以将task看做sink。进一步讲,M标识所有task在并行执行期间的记录。用t来表示task,每个t都属于T,代表一个算子的独立执行实例。t由以下部分组成:(1)输入通道It,输出通道Ot的集合,且It,Ot属于E;(2)一个算子的状态St。(3)一个用户定义的方法(UDF)ft。数据摄取是采用拉的方式:在执行阶段,每个task消费输入数据,更新自己的算子状态,并生成新的数据传输给用户定义的函数。更明确的说,每个记录r属于M,T中的任务t接收到记录r,t的状态变成St*,t生成新的数据记录D,并将D传递给用户自定义函数ft,D属于M。ft:
在这里插入图片描述

4 异步栅栏快照(Asynchronous Barrier Snapshotting,ABS)

为了提供一致性结果,分布式处理系统需要能够恢复失败的task。周期性的获取执行图的快照,并在失败时进行恢复操作,是一种提供恢复能力的方法。一个快照是执行图的全局状态,捕获了所有重启到指定执行状态所必须的信息。

4.1 问题定义

我们定义全局快照
在这里插入图片描述
是执行图G = (T,E)所有task和边的状态。更详细的说,T包含所有的算子状态
在这里插入图片描述
E
包含了所有通道的状态
在这里插入图片描述
其中e包含了数据记录。
我们需要每个快照G
都保持这些信息,从而能够确保在终止后的恢复操作结果正确。
如果所有进程都是活动的,则在启动后的有限时间内结束。可行性表示快照的意义,即在快照过程中没有丢失关于计算的信息。从形式上讲,这意味着在快照中维护了因果顺序[9],以便在任务中交付的记录也从快照的角度发送。

4.2 无环数据流ABS

当执行被分为若干个阶段(stage),那么我么就有可能不保存通道状态的情况下做快照。stage将注入的数据流拆分开,并把分开的数据和他们相关的计算结合到一起形成一系列的执行。在这些执行中,所有先前输入和产生的结果已经被充分处理。每个stage结尾的operator状态的集合反映了整个执行历史,因此可以作为快照信息。我们算法的核心理念是,当持续数据集成时,通过分期快照创建一致快照。
在我们的方法中,通过使用特殊的栅栏标签周期的切入数据流中,从而将持续数据流拆分成不同的分期阶段。这个操作会贯通整个执行图,一直到sink节点。全局快照将会在task接收到代表分期的栅栏标签时,增量的进行构建。对于算法,我们做了如下假设:

在这里插入图片描述
算法1 无环执行图的异步栅栏快照

1: upon event hInit | input channels, out-
put channels, fun, init statei do
2: state := init state; blocked inputs := / 0;
3: inputs := input channels;
4: outputs := output channels; udf := fun;
5:
6: upon event hreceive | input, hbarrierii do
7: if input 6= Nil then
8: blocked inputs := blocked inputs ∪
{input};
9: trigger hblock | inputi;
10: if blocked inputs = inputs then
11: blocked inputs := / 0;
12: broadcast hsend | outputs, hbarrierii;
13: trigger hsnapshot | statei;
14: for each inputs as input
15: trigger hunblock | input i;
16:
17:
18: upon event hreceive | input, msgi do
19: {state 0 ,out records} := udf(msg,state);
20: state := state 0 ;
21: for each out records as {output,out record}
22: trigger hsend | output, out recordi;
23:
24:
  • 网络通道是可信的。一个FIFO的消息时可以被blocked和unblocked。当一个通道blocked,所有的消息将会被缓存,不被传递。当通道unblocked,则消息会继续传播。
  • task可以触发其通道的操作,比如block,unblock以及发送消息。也支持向所有的输出通道广播消息。
  • 源数据task通过Nil的输入通道注入消息。

ABS算法1执行步骤如下:一个中心协调者周期的向所有源task注入阶段栅栏。当一个源task接收到栅栏,他将会为当前状态做一个快照,之后将栅栏消息光波导所有的输出通道(如图2)。当一个非源task从一个输入通道接收到栅栏消息,将会阻塞这个通道,直到所有的输入通道都接受到这个栅栏消息。这时,task会对当前的状态执行一个快照,并将栅栏消息广播到所有的输出通道。之后task将会unblock所有的输入通道,继续进行计算操作。全局快照 G ∗ = (T ∗ ,E ∗ )将会在所有的算子状态T都完成,且E=空的时候完成。

证明草图:诚如之前介绍的快照算法应该支持可中执行和可行性。可终止性通过通道和无环执行图的属性来保证。通道的可靠性确保只要task还运行,每个栅栏消息最终都会被接收。进一步说,一定存在从源开始,经历拓扑图中所有的task,直到结束的路径存在。

对于可行性,只要能够让全局快照的算子状态反映最后阶段的历史数据就可以。这需要通道的FIFO特性以及输入通道在接到栅栏消息的阻塞特性实现的。
在这里插入图片描述

4.3 有环数据流ABS

在有向有环图的执行图中,ABS算法将不会停止,最终导致死锁。原因是环中的task会无限的等待所有输入通道的栅栏消息。
下图为算法。由于实际工作中不涉及,所以忽略这部分。

Algorithm 2 Asynchronous Barrier Snapshotting for
Cyclic Execution Graphs
1: upon event hInit | input channels,
backedge channels, output channels, fun,
init statei do
2: state := init state; marked := / 0;
3: inputs := input channels; logging := False;
4: outputs := output channels; udf := fun;
5: loop inputs := backedge channels;
6: state copy := Nil; backup log := [];
7:
8: upon event hreceive | input, hbarrierii do
9: marked := marked∪{input};
10: regular := inputs\loop inputs;
11: if input 6= Nil AND input / ∈ loop inputs then
12: trigger hblock | inputi;
13: if ¬logging AND marked = regular then
14: state copy := state;logging := True;
15: broadcast hsend | outputs, hbarrierii;
16: for each inputs as input
17: trigger hunblock | input i;
18:
19: if marked = input channels then
20: trigger hsnapshot | {state copy,
backup log}i;
21: marked := / 0;logging := False;
22: state copy := Nil;backup log := [];
23:
24: upon event hreceive | input, msgi do
25: if logging AND node ∈ loop inputs then
26: backup log := backup log :: [input];
27: {state 0 ,out records} := udf(msg,state);
28: state := state 0 ;
29: for each out records as {output,out record}
30: trigger hsend | output, out recordi;
31:
32:

5 错误恢复

虽然这部分内容不是工作的中点,但是有效的错误恢复机制可以促进我们快照算法的引用。因此我们对错误恢复的操作方式提供了简要的描述。对于一致性快照,这里有若干个错误恢复机制。最简单的机制是从最后一个全局快照重启整个执行图:(1)每个任务都从持久化存储获得快照St,并将自己的状态恢复到St状态。(2)恢复备份日志和处理所包含的数据。(3)开始从输入通道采集数据。

另一种是类似于TimeStream的局部图恢复机制。本机制重新调度失败任务的上行task,直到根节点。一个恢复计划示例如图4。为了满足exactly-once语义,下行节点收到的重复数据应该被忽略,从而避免重复计算。为了达到此目的,我们可以遵从类似于SDG机制,从数据源开始为每个数据都进行编号。因此,每个下行节点都可以忽略掉编号小于已处理编号的数据。
在这里插入图片描述

6 实现

我们将ABS算法的实现贡献给Apache Flink,从而为其提供了exactly-once执行语义的实现。在我们目前的实现中,将所有blocked通道的输入数据都存储在了磁盘上,而不是保存在内存中。这样增加了可扩展性。虽然这种方式增加了程序的鲁棒性,但是却影响了ABS算法的运行时性能。

为了区分算子的状态和数据,我们采用了一个明确的接口:OperatorState。接口包括更新和核对状态的方法。我们为Apache Flink有状态的运行时算子提供了OperatorState的实现,如基于偏移量的源或聚合。

快照协作作为一个进程运行在Job manager上,它为一个独立的job执行图保持了全局状态。协调者将阶段栅栏周期的切入执行图的所有源数据中。依据不同的配置,上一个全局快照状态存放在算子中,或一个分布式的内存持久化数据库中。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值