这是我参与「第四届青训营 」笔记创作活动的第三天
上一篇文章提到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完成的通知时,它才正式提交事务,实现结果的真正写入。