Exactly Once 语义在 Flink 中的实现 | 青训营笔记
这是我参加「第四届青训营」笔记创作活动的的第2天。
01. 数据流和动态表
如何在数据流上执行SQL语句,说明流式处理中的概念
1.1 随处可见的流式数据
GPS 物联网 银行数据
1.2 传统SQL和流处理
| 特征 | SQL | 流处理 |
|---|---|---|
| 处理数据的有界性 | 处理的表是有界的 | 流是一个无限元组序列 |
| 处理数据的完整性 | 执行查询可以访问完整的数据 | 执行查询无法访问所有的数据 |
| 执行时间 | 产生固定大小结果后终止 | 不断更新结果,永不终止 |
问题:如何定义流处理中的表呢?有什么特点呢?
1.3 概述-数据流和动态表转换 - 重点
从上图我们要有一个认识:表和流是可以动态转换的
下文将介绍各个部分。
在流上定义表
下图显示了单击事件流如何转换为表。当插入更多的单击记录时,结果表将不断增长。
- 动态表 Dynamic Table : 随时间不断变化的表,在任意时刻,可以像查询静态批处理表一样查询它们
1.4 连续查询
动态表-->新动态表
- 查询不会终止
- 查询结果会不断更新,产生一个新的动态表
1.5 查询产生仅追加数据的动态表
没有重叠的窗口
时间窗口不一样,不会更新之前的记录
1.6 两个连续查询对比
- 第一个查询更新先前输出的结果,即定义结果表的changelog 流包含INSERT 和UPDATE 操作;
- 第二个查询只附加到结果表,即changelog流只包含 INSERT 操作。
1.7 Retract 消息的产生
动态表到实时流的转换
- Append-only Stream: Append-only 流(只有 INSERT 消息)
- Retract Stream: Retract 流(同时包含 INSERT 消息和 DELETE 消息)
回撤消息,对之前结果进行更新
当表中第三行再次出现Mary时,对Mary这条消息进行回撤(delete),并更新cnt值,由1变为2
1.8 状态 state
在流式计算中,会存在有状态的计算逻辑(算子)
比如,需要计算某个用户在网上的点击量,该用户在网站当前的总点击次数就是算子状态,对于新的输入数据,先判断是否是该用户的点击行为,如果是,则将保留的点击次数(状态)增加一,并将当前累加结果输出。
1.9 回顾
- 数据流和动态表之间的转换
- 在数据流的查询不会终止
- 查询可能会有状态,用来不断更新查询的结果
1.10 不同数据处理保证的语义
-
At-most-once:每条数据消费至多一次,处理延迟低。 出现故障,什么也不做
-
At-least-once:每条数据消费至少一次,一条数据可能存在重复消费
-
Exactly-once:最严格的处理语义。每条数据都被消费且仅被消费一次,仿佛故障从未发生
02. Exactly-Once 和 Checkpoint
2.1 状态快照与恢复
例子:对输入流分别计算奇数和与偶数和
保存当前的状态,当发生错误时,可以从该状态重新开始执行
2.2 制作快照的时间点
一个简单的快照制作算法:
- 暂停处理输入的数据
- 等待后续所有处理算子消费当先已输入的数据
- 待2处理完后,作业所以算子复制自己的状态并保存到远端可靠存储
- 恢复对输入数据的处理
2.3 Chandy-Lamport 算法
-
快照制作的开始
每一个source算子都接收到 JM 发送的Checkpoint Barrier 标识状态快照制作的开始
-
Source 算子的处理
各个 source 保存自己的状态后, 向所有连接的下游继续发送 Checkpoint Barrier,同时告知 JM自己状态以及制作完成
-
Barrier Alignment
-
算子会等待所有上游的barrier 到达后才开始快照的制作
-
已经制作完成的上游算子会继续处理数据,并不会被下游算子制作快照的过程阻塞
-
说明:这里Sum even算子中,数据’4‘虽然已经到达,但仍有barrier未到达,所以暂时还不进行数据处理
-
-
快照制作和处理数据的解耦
-
Checkpoint 的结束 所有算子都告知 JM 状态制作完成后,整个 Checkpoint就结束了。
2.4 Checkpoint 对作业性能的影响
-
解偶了快照制作和数据处理过程,各个算子制作完成状态快照后就可以正常处理数据,不用等下游算子制作完成快照
-
在快照制作和 Barrier Alignment 过程中需要暂停数据处理,仍然会增加数据处理延迟
-
快照保存到远端也有可能极为耗时
03. 端到端 Exactly-Once 实现
3.1 端到端 Exactly-Once 语义
- Checkpoint 能保证每条数据都对各个有状态的算子更新一次, sink输出算子仍可能下发重复的数据
- 严格意义的端到端的 Exactly-Once 语义需要特殊的 sink 算子实现
- eg. 保证 A账户-500 B账户+500
3.2 两阶段提交协议
-
Coordinator:协作者,同步和协调所有节点处理逻辑的中心节点
-
Participant:参与者,被中心节点调度的其他执行处理逻辑的业务节点
(一)预提交阶段
- 协作者向参与者发送commit消息
- 协作者执行事务,但不是真正提交
- 若成功,发送成功的消息 vote yes,失败 vote no
(二)提交阶段
- 若协作者成功接收所有参与者vote yes 的消息
- 协作者向参与者发送commit消息
- 收到commit的参与者释放执行事务所需要的资源,并结束这次事务的执行
- 发送ack消息
- 协作者收到ack消息,标识该事务执行成功
- 若收到vote no消息:
- 协作者向所有参与者发送 rollback 消息
- 参与者回滚事务的执行操作,并释放占用资源
- 发送ack消息
- 协作者收到ack消息,标识该事务成功完成回滚
3.3 两阶段提交协议在 Flink 中的应用
-
Flink 中协作者和参与者的角色分配
-
协作者(JobManager)发起阶段一提交
-
各算子 Checkpoint 的制作
-
提交阶段及 Checkpoint 的制作完成
04. Flink案例讲解
4.1 账单计算服务
场景简介
从kafka中读取账单消息,进行处理后写入到MySQL中
当前方案
- 在上次记录的位点之后,从kafka中读取固定大小的数据
- 对该批数据进行去重和聚合计算
- 处理完成后写入MySQL中,若全部写入成功,则记录当前读取到的消息的终止位置;若处理或写入失败,则不记录位点
- 跳回步骤一
存在的问题
- 非严格意义上的端到端的 Exactly-Once 语义:若该批数据处理完后,在写入MySQL中发生异常,则存在部分数据写入的情况,下次作业启动,这部分数据仍然会重复写入;
- 去重能力有限:只能在当前处理的一批数据内去重,无法在批与批之间进行去重;
Flink方案
- 优势:
-
严格意义上的端到端的 Exactly-Once 语义:下游读到的数据是不丢不重的;
-
增强的去重能力:可以在更长的时间维度对数据进行去重
总结
- 数据流可以转换成动态表,动态表也可以重新转换成数据流
- 处理无限数据流的算子可以是有状态的
- Flink通过 CheckPoint 机制实现故障前后的状态快照制作和恢复
- 支持两阶段提交协议的下游存储可以结合Flink Checkpoint 机制实现严格意义上的端到端的 Exactly-Once 语义实现