Chandy-Lamport算法以及两阶段提交协议 | 青训营笔记

361 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第三天

上一篇文章提到Flink通过设置checkpoint来辅助保存状态快照来实现容错,可是,这样的话,很有可能前面的任务已经处理好了这条数据,而后面的任务还没处理完那条数据,那么前面的任务就等待后面的任务,白白浪费了资源,这样显然是不够高效的,对于这样的局面,Flink应用了Chandy-Lamport算法。

Chandy-Lamport算法

JobManager在数据流中插入一个特殊的数据结构-barrier。在Flink中,watermark和barrier的意义类似,都是截止时间的标志。

各个Source任务保存自己状态后,向所有连接的下游继续发送Barrier(广播),同时告知JM自己状态已经制作完成,之后的所有任务只要遇到它就开始对状态做持久化快照保存。

Barrier Alignment

下游的算子会等待上游的barrier到达后才开始快照的制作,并且,已经制作完成的上游算子会继续处理数据,并不会被下游算子制作快照的过程阻塞。

因此Chandy-Lamport算法实现了在不暂停整体流处理的前提下,将状态备份保存到检查点。

当所有算子都告知JM状态制作完成后,整个Checkpoint也就结束了。这时,所有算子保存状态到持久化存储。

注:在Barrier Alignment期间,新来的数据是存放在缓存的。

总结:Chandy-Lamport算法的最大意义,就是解耦了快照制作和数据处理过程,各个算子制作完成状态快照后就可以正常处理数据,不用等下游的算子制作完成快照。

但是,在快照制作和Barrier Alignment过程中需要暂停处理数据,仍然会增加数据处理延迟,并且,快照保存到远端也有可能极为耗时,这在某些场景下是不容接受的。

端到端的exactly-once语义

  要想实现端到端的状态一致性,不仅要求Flink内部保证exactly-once(得益于checkpoint机制),还要求source端和sink端也实现exactly-once。

对于Source端来说,要求Source端能够重设读入数据的偏移量offset,也就是数据的读取位置。

而对于Sink端,要求从故障恢复时,数据不会重复写入外部系统

那么,该如何实现严格意义的端到端的Exactly-once语义需要特殊的sink算子呢?

两阶段提交协议(2PC)

分为两个阶段:

(一)   预提交阶段:

1.      协作者向所有参与者发送一个commit消息;

2.      每个参与的协作者收到消息后,执行事务,但是不真正提交;

3.      若是五成功执行完成,发送一个成功的消息(vote yes);执行失败,则发送一个失败的消息(vote no)

(二)   正式提交阶段

若协作者成功接收到所有的参与者vote yes的消息:

1.      协作者向所有参与者发送一个commit消息;

2.      每个收到commit消息的参与者释放执行事务所需的资源,并结束这次事务的执行;

3.      完成步骤2后,参与者发送一个ack消息给协作者;

4.      协作者收到所有参与者的ack消息后,表示该事务执行完成

若协作者成功接收到所有的参与者vote no的消息(或者发生等待超时):

1.      协作者向所有参与者发送一个rollback消息;

2.      每个收到rollback消息的参与者回滚事务的执行操作,并释放执行事务所占的资源;

3.      完成步骤2后,参与者发送一个ack消息给协作者;

4.      协作者收到所有参与者的ack消息后,表示该事务成功完成回滚

  总结:对于每个checkpoint,sink任务会启动一个事务,并将接下来所有接收的数据添加到事务里。然后将这些数据写入外部sink系统,但不提交它们-这时只是“预提交”,当它收到checkpoint完成的通知时,它才正式提交事务,实现结果的真正写入。