💨数据流和动态表
传统的SQL:🚩处理的 表是有界的🚩执行查询可以访问完整的数据🚩批处理查询产生固定大小结果后终止流处理:🚩流是一个无限元组序列🚩执行查询无法访问所有数据🚩查询不断更新结果,永不终止
👇数据流和动态表转换如下
不同数据处理保证的语义
✔At-most-once:出现故障的时候,啥也不做。数据处理不保证任何语义,处理时延迟低。
✔At-least-once:保证每条数据均至少被处理一次,一条数据可能存在重复消费。
✔Exactly Once:最严格的处理语义,从输出结果来看,每条数据均被消费且仅消费一次,仿佛故障丛飞发生。
💨Exactly Once和Checkpoint
从上面三个语义中我们看到Exactly Once语义是标准最高的。
比如说,我们可以把一个记录处理操作再划分为下面3个子操作: 🚩接收数据的操作。从数据源接收数据的操作。 🚩转换处理数据的操作。在这里面数据会被事先定义好的各种操作语义所处理。 🚩输出数据操作。将处理好后的结果数据输出到外部系统文件系统,或数据集等等。
其实在流处理过程中,失败现象就可能发生在上面3个步骤中的任何一步。如果要拿最高标准“Exactly Once”标准来看,我们要达到的理想效果应该是:
🚩数据只被处理过一次,这里面可以包括曾经处理失败,然后再读取原始数据进行处理。
🚩对于一个原始数据,我们保证最后结果数据输出是一致的,我们并不是说输出操作只是一次执行的。
🎈Checkpoint是Flink的一致性检查点。
Flink检查点的核心作用是确保状态正确,即使遇到程序中断,也要正确。记住这一基本点之后,我们用一个例子来看检查点是如何运行的。Flink为用户提供了用来定义状态的工具。
🚩Checkpoint是Flink 故障恢复机制的核心,就是应用状态的一致性检查点
🚩有状态流应用的一致检查点,其实就是所有任务的状态,在某个时间点的一份拷贝(一份快照);这个时间点,应该是所有任务都恰好处理完一个相同的输入数据的时候(如5这个数据虽然进了奇数流但是偶数流也应该做快照,因为属于同一个相同数据,只是没有被他处理)
🚩在JobManager中也有个Chechpoint的指针,指向了仓库的状态快照的一个拓扑图,为以后的数据故障恢复做准备
💨Chandy-Lamport分布式快照算法
✔分布式快照:特定时间点记录下来的分布式系统的全局状态(global state)。
✔分布式快照主要用途:故障恢复(即检查点)、死锁检测、垃圾收集等。
✔将分布式系统抽象为一张有向图:顶点称为进程(process),边称为channel。
Chandy-Lamport算法基于如下前提:在每对进程pi、pj之间都存在channel cij和cji,cij是output,cji是input。channel的网络可靠,缓存无限大,并且先进先出,即channel上的消息会不重不漏地按序到达。算法要达到如下的终极目标: 🚩最终产生的快照必须保证一致性; 🚩快照过程不能影响系统正常运行,更不能stop the world。 Chandy-Lamport 算法具体的工作流程主要包括下面三个部分: 🚩Initiating a snapshot: 也就是开始创建 snapshot,可以由系统中的任意一个进程发起🚩Propagating a snapshot: 系统中其他进程开始逐个创建 snapshot 的过程 🚩Terminating a snapshot: 算法结束条件
💨Flink端到端的Exactly-once语义
要真正实现流数据处理的Exactly-Once语义,一共要保证三个地方
🚩外部应用数据输入到source要保证Exactly-Once语义。🚩flink内部的数据处理要保证Exactly-Once语义。🚩sink将数据输入到外部应用要保证Exactly-Once语义。
🎈那么flink对这三个阶段是如何保证的呢?这里以kafka-flink-kafka为例简单概述:
🚩内部 —— 利用checkpoint机制,把状态存盘,发生故障的时候可以恢复,保证内部的状态一致性🚩source —— kafka consumer作为source,可以将偏移量保存下来,如果后续任务出现了故障,恢复的时候可以由连接器重置偏移量,重新消费数据,保证一致性🚩sink —— kafka producer作为sink,采用两阶段提交 sink,需要实现一个 TwoPhaseCommitSinkFunction
💨两阶段提交协议
在多个结点参与执行的分布式系统中,为了协调每个节点都能同时执行或者回滚某个事务性的操作,引入了一个中心节点来统一处理所有节点的执行逻辑,这个中心节点叫做协作者,被中心节点调度的其他业务节点叫做参与者。
阶段提交协议分为:预提交阶段和提交阶段
👇预提交阶段
🚩协作者向所有参与者发送一个commit消息🚩每个参与的协作者收到消息后,执行事务,但是不真正提交🚩若事务成功执行完成,发送一个成功的消息;执行失败,则发送一个失败的消息
👇提交阶段
🍳若协作者成功接收到所有参与者的vote yes的消息:
🚩协作者向所有参与者发送一个commit消息;🚩每个收到 commit消息的参与者释放执行事务所需的资源,并结束这次事务的执行;🚩完成步骤2后,参与者发送一个 ack消息给协作者;🚩协作者收到所有参与者的ack消息后,标识该事务执行完成。
🍳若协作者有收到参与者vote no的消息(或者发生等待超时) :
🚩协作者向所有参与者发送一个 rollback消息;🚩每个收到rollback 消息的参与者回滚事务的执行操作,并释放事务所占资源;🚩完成步骤2后,参与者发送个ack消息给协作者;🚩协作者收到所有参 与者的ack消息后,标识该事务成功完成回滚。
💨两阶段提交总结
🚩事务开启:在sink task向下游写数据之前,均会开启一 个事务,后续所有写数据的操作均在这个事务中执行,事务未提交前, 事务写入的数据下游不可读;
🚩预提交阶段: JobManager 开始下发Checkpoint Barrier,当各个处理逻辑接收到barrier 后停止处理后续数据,对当前状态制 作快照,此时sink也不在当前事务下继续处理数据(处理后续的数据需要新打开下一个事务)。状态制作成功则向JM成功 的消息,失败则发送失败的消息;
🚩提交阶段: 若JM收到所有预提交成功的消息,则向所有处理逻辑(包括sink)发送可以提交此次事务的消息,sink 接收到此 消息后,则完成此次事务的提交,此时下游可以读到这次事务写入的数据;若JM有收到预提交失败的消息,则通知所有处理 逻辑回滚这次事务的操作,此时sink则丢弃这次事务提交的数据下。
- Flink Checkpoint机制的存在,是为了解决Flink任务在运行的过程中,由于各种问题导致任务异常中断后,能正常恢复。
- Checkpoint能保证每条数据都对各个有状态的算子更新一次,sink 输出算子仍然可能下发重复的数据;
- 严格意义的端到端的 Exactly-once语义需要特殊的sink 算子实现。