最近在学习Flink的Fault Tolerance,了解到Flink在Chandy Lamport Algorithm的基础上扩展实现了一套分布式Checkpointing机制,这个机制在论文"Lightweight Asynchronous Snapshots for Distributed Dataflows"中进行了详尽的描述。
Chandy Lamport Algorithm
所以说,大神的世界我们不懂,一言不合就写一篇论文。我们言归正传,开始介绍论文中描述的算法。
分布式系统模型和状态定义
分布式系统模型
分布式系统是一个包含有限进程和有限消息通道的系统,这些进程和通道可以用一个有向图描述,其中节点表示进程,边表示通道。如下图所示:p、q分别是进程,c, c'则是消息通道。
另外为了问题描述的简洁,对上述模型还做了假设:消息通道只包含有限的buffer、消息保序、通道可靠等
分布式系统状态(State)
- Event:分布式系统中发生的一个事件,在类似于Flink这样的分布式计算系统中从Source输入的新消息相当于一个事件。在论文中作者给出了数学化的定义,具体参考论文。
- 进程状态:包含一个初始状态(initial state),和持续发生的若干Events。初始状态可以理解为Flink中刚启动的计算节点,计算节点每处理一条Event,就转换到一个新的状态。
- 通道状态:我们用在通道上传输的消息(Event)来描述一个通道的状态。
Distributed Snapshots
有了系统状态和模型的定义,终于可以开始介绍分布式快照的算法了。对于一个分布式快照算法,我们有如下的两点要求:
- 正确性:能够准确的记录每一个进程、通道的状态,同时通过这些局部状态,能够准确表达一个分布式系统的全局状态。这里碰到的挑战是,每个进程、通道没法同时记录自身的状态,因为我们没有一个全局的时钟来保持状态记录的同步。
- 并行性:快照操作与分布式系统计算同时运行,但不能影响所有系统的正常功能,对性能、正确性等无影响。
按照上一小节的描述,全局状态是进程和通道状态的组合,在论文中,作者证明了通道状态可以通过记录进程状态来记录和恢复,并提出了下述的分布式snapshot算法:
对于进程p、q,p->q通过通道c连接,通过以下步骤记录global state
// 进程p行为,通过向q发出Marker,发起snapshot
begin
p record its state;
then
send one Marker along c after p records its state and before p sends further messages along c
end
//进程q接受Marker后的行为,q记录自身状态,并记录通道c的状态
if q has not recorded its state then
begin
q records its state;
q records the state c as the empty sequence
end
else q records the state of c as the sequence of messages received along c after q’s state was recorded and before q received the marker along c.
关于Chandy-Lamport Algorithm的主要介绍就到这里,论文中还有一些关于某些特殊情况的证明,大家有兴趣可以参考论文。
Flink Checkpointing的实现原理
Flink流式计算模型
Asynchronous Barrier Snapshots
在ABS算法中用Barrier代替了C-L算法中的Marker,针对DAG的ABS算法执行流程如下所示:
// 初始化Operator
upon event (Init | input channels, output
channels, fun, init state)
do
state := init_state;
blocked_inputs := {};
inputs := input_channels;
out_puts := out_put channels;
udf := fun;
// 收到Barrier的行为
upon event (receive | input, (barrier))
do
//将当前input通道加入blocked 集合,并block该通道,此通道的消息处理暂停
if input != Nil then
blocked inputs := blocked inputs ∪ {input};
trigger (block | input);
//如果所有的通道都已经被block,说明所有的barrier都已经收到
if blocked inputs = inputs then
blocked inputs := {};
broadcast (send | outputs, (barrier)); //向所有的outputs发出Barrier
trigger (snapshot | state); //记录本节点当前状态
for each inputs as input //解除所有通道的block,继续处理消息
trigger (unblock | input);
不过Flink还是提供了选项,可以关闭Exactly once并仅保留at least once,以提供最大限度的吞吐能力。




