03-Exactly Once 语义在 Flink 中的实现 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的第2天
1.数据流和动态表
如何在数据流上执行SQL语句,说明流式处理中状态的概念
SQL结构化语言,使用门槛较低。流是源源不断地,需要有状态的概念。
1.1 随处可见的流式数据
比如银行的存钱和计费服务,随时可用,任意时刻可以发生。
1.2 传统SQL和流处理
| 特征 | SQL | 流处理 |
|---|---|---|
| 处理数据的有界性 | 处理的表是有界的 | 流是一个无限元组序列 |
| 处理数据的完整性 | 执行查询可以访问完整的数据 | 执行查询无法访问所有的数据 |
| 执行时间 | 批处理查询产生固定大小结果后终止 | 查询不断更新结果,永不终止 |
如何在实时流上定义表?
1.3 概述-数据流和动态表转换
某一时刻表的状态相当于一张静态表。
动态表:与表示批处理数据的静态表不同,动态表是随时间变化的。可以像查询静态批处理表一样查询它们。 数据库表是INSERT、UPDATE 和 DELETE DML 语句的 stream 的结果,通常称为 changelog stream。
1.4 连续查询
在任何时候,连续查询的结果在语义上与以批处理模式在输入表快照上执行的相同查询的结果相同。
连续查询的特点是:
- 查询从不终止
- 查询结果会不断更新,产生一个新的动态表
1.10 不同数据处理保证的语义
-
At-most-once
- 数据可能丢失,保证所有的时间都用来处理数据流,不需要处理故障和恢复的额外开销
-
At-least-once
- 保证每条数据均至少被处理一次,一条数据可能存在重复消费。
-
Exactly-once
- 最严格的处理语义,从输出结果来看,每条数据均被消费且仅消费一次,仿佛故障从未发生。
2.Exactly-Once和Checkpoint
当故障发生时,Flink自身如何从故障中恢复,保证对数据消费的不丢不重
计算是有状态的,如何保证对数据的语义不丢不重。设置一个故障恢复的时间点。当发生故障时,回到那个时间点并恢复状态。
source算子负责读取数据流。source需要记录消费的时间位点,比如图中表示当前已经消费到5这个数字了。
sum_even算子是偶数累加器。(2+4)
sum_odd算子是奇数累加器。(1+3+5)
同样对于累加器而言需要知道当前累加的和是多少,下次才能计算出正确的结果
状态保存在一个可靠的远端存储系统中。
消费6没有问题,消费7时奇数累加器挂了。使用最近的时间点恢复。
将状态对应的值从外部存储系统中读出来并重新赋值给对应的算子。
2.1 状态快照与恢复
2.2 制作快照的时间点
最直观的想法:在作业运行的任意时刻去把状态保留下来,保留到远端存储,发生故障的时候从远端恢复?
举个例子,5这个数据已经被source读到了,但是并没有发送到下游,下游奇数累加器的结果还是4,这个时候保留的状态是5 6 4到远端存储。这样恢复的时候source认为5已经发送过了,不在考虑,因此直接跳过5开始准备发送6,这样5就没有被下游的奇数累加器处理到。
source进入消费位点之后的所有数据都被下游处理过了,否则可能会出现某些数据没有被下游算子处理到的问题。
source暂停处理
不用等待下游处理完成,一旦自己的数据处理完毕,就将checkpoint barrier往下游发
由 statebacked 将快照保存到远端,异步保存
03.端到端Exactly-Once实现
Flink本身的Checkpoint机制如何和外部存储结合,实现端到端的不丢不重语义
有一部分数据对sink而言是重复下发的。
将两阶段协议结合sink,就能实现端到端的Exactly-Once。
JobManager就对应协作者,计算节点对应参与者。
状态制作执行成功向JM发送一个制作成功(VOTE YES)的消息,否则发送一个制作失败(VOTE NO)的消息。
只有真正提交之后数据对下游才是可见的。
04.Flink案例讲解
选择字节内部真实的案例场景,介绍Flink如何解决和实现账单计费服务
上游 at least once,可能会有重复消息,全局唯一的uuid,不同的数据带不同的值,去重,针对某一个场景进行聚合,将结果写入MySQL并进行扣费服务。
幂等和去重。