本篇笔记对字节跳动青训营7.24日成聪老师讲授的《Exactly-Once 语义在 Flink 中的实现》内容做一个复习总结。
数据流和动态表
流式数据
- 传统的SQL处理的数据是有界的,静止的,批处理查询产生固定大小结果后终止。
- 流处理是无限元组序列,一直在动态增加数据,无法访问所以数据,查询结果不断更新。
- 数据流和动态表是可以动态进行转换的:数据流转换为动态表,SQL查询在动态表上执行,连续带状态的Query的结果表达为动态表,最后写出为数据流。
- 动态表随时间变化,也可以用静态的批处理方式查询动态表
- SELECT user, COUNT( url )AS cnt ... 连续查询结果不断更新,和以批处理模式的结果一致。
- SELECT user, TUMBLE_END ( cTime, INTERVAL '1' HOURS ) AS endT,COUNT( url )AS cnt ... 这里是在统计TUMBLE窗口大小一小时内用户的点击次数,这里提到了滑动窗口的概念,查询产生仅追加数据的动态表,对之前窗口不再改变。
- 这两种连续查询第一种会不断更新,包含了 INSERT 和 UPDATE 第二种只进行追加,只包含INSERT操作。
- Retract消息(回撤):先回溯,减1之后再更新+2
- 状态:流数据遇到新增时需要查之前的状态和存储结果,后续才能正确计算。
- 不同数据处理保证的语言:At-most-once(不处理故障) / At-least-once(可能重复消费) / Exactly-once
Exactly-Once 和 Checkpoint
- 状态恢复的时间点:所有处理逻辑消费完成(不能前后不统一),保留状态及数据。
- 原始的快照制作需要暂停处理 输入的数据,待所有算子消费完已输入数据,上传每个算子的状态,然后再恢复处理数据。
- Chandy-Lamport算法:JobManager 向所有 Source 发送 Checkpoint Barrier 标识,Source 短暂停止,状态保存后下发 Checkpoint ,不再等待后续完成(无阻塞),继续处理。每个算子等待所有上游 barrier 到达才开始制作快照,在此之前缓存数据但不处理(保证状态一致),所有算子通知 JM 状态制作完成后结束。
(PS:Barrier Alignment 过程需要等待所有上游下发checkpoint,此时的暂停数据处理仍然会增加数据处理的延迟,快照保存远端过程可能耗时大)
图源:Stream Processing with Apache Flink by Fabian Hueske Vasiliki Kalavri
Flink 端到端的 Exactly—once 语义
- 对于上图的过程,Checkpoint 能保证对于每条数据更新,但是 sink 输出算子仍然可能下发重复的数据,严格意义的端到端 Exactly-once 还需要特殊的 sink 算子实现。
两阶段提交协议:多个节点执行的分布式系统中,通过一个中心节点-协作者(Coordinator)统一管理执行或者回滚某个事务性操作(保证原子性),其他节点叫做参与者(Participant)
- 协作者向所有参与者发 query to commit,参与者执行之不提交,成功执行返回 vote yes,失败 vote no。
- 执行者收到所有 vote yes 发送 commit,参与者收到 commit 释放执行所需资源,结束事务并发回执行者 ACK,执行者收到所有 ACK,标识事务执行完成。
- 只要有一个 vote no 发送 rollback,每个参与者收到后回滚,释放资源,返回 ACK,收到所有 ACK,标识事务回滚成功。
- Flink 中的两阶段提交协议 sink:JM 作为协作者,直到 sink 之前是 checkpoint 机制,sink 不断向 kafka 写数据,但因为未提交下游还不可读,直到 JM 通知事务提交,下游才可见,如果失败回滚,kafka 标记为丢弃数据,读到时数据跳过。(事务产生延迟,下游无法及时消费数据,多用于 Flink 中的批处理)
图源:An Overview of End-to-End Exactly-Once Processing in Apache Flink® (with Apache Kafka, too!)
案例
- Kafka 读用户数据 At-least-once,由 Kafka 幂等性,消息自身有 uid,下游可以由此实现去重,根据场景和应用聚合,写入 mysql。此案例未使用 Flink,执行步骤是批处理的:记录执行位点后读数据,去重聚合写入,成功记录,失败回滚。( mysql 重复读写无影响,实现了端到端 Exactly-once,去重能力有限,批与批去重无法实现)
- Flink 的状态去重能力更强,但是不可能完全去重,否则缓存状态量过大。
问题:端到端的 Exactly-once 仍然只能在批处理实现,那 Flink 在流处理上是不是严格意义上没有 Exactly-once ?