Exactly Once|青训营笔记

119 阅读5分钟

这是我参与「第四届青训营 」笔记创作活动的的第2天

1. 数据流和动态表

流式处理主要依靠的状态:

特点SQL流处理
有界性无限元组序列
完整性执行查询可以访问完整的数据无法访问所有数据
执行时间固定大小结果而终止不断更新,永不终止

整体流程如下:

graph TD
Stream --> DynamicTable --> ContinuousQuery+State --> DynamicTable --> Stream
  • 动态表的特点
    • 随时间变化
    • 在特定时间视为静态表,查询批处理,连续查询
    • 查询方式(以点击网站次数为例):
      • 不断对动态表进行计数更新;
      • 不更新结果,设定时间间隔计数,只需要在表后插入数据;
      • Retract:更新计数之前先回溯表格的内容
  • 语义
    1. At-most-once:不管故障继续执行,处理时延低,准确性低
    2. At-least-once:至少处理一次,重复消费,准确性高
    3. Eactly-once:最严格,每条数据至少且最多消费一次。

2. Exactly-Once 和 Checkpoint

依赖状态快照与恢复

  • 算法1:
    • 暂停所有输入 -> 等待后续逻辑处理,上传快照 -> 恢复状态 -> 恢复对输入数据处理
    • 时间点:等待处理逻辑消费完成source保留状态之前的数据
  • Chandy-Lamport
    • 快照制作开始:
      JM向source发送Checkpoint Barrier。
    • source算子处理:
      停止处理,保存状态给state backend,向所有下游传输barrier; 继续处理后续数据。
    • Barrier Alignment: 算子等待所有上游Barrier到达后才开始快照制作,上游数据到达会进行缓存;
    • 所有算子告知JM状态完成,checkpoint结束
  • 作业性能:
    1. 不用等下游算子全部制作完成
    2. 快照制作和Barrier Alignment依旧需要暂停,增加数据处理延迟
    3. 快照保存到远端,极为耗时

3. Flink 端到端Exactly-Once 实现

  • 端到端Exactly-Once语义
    1. Checkpoint 保证每条数据对算子更新一次,但sink输出可能下发重复数据
    2. 严格意义需要特殊sink算子
  • 两阶段提交协议
    • 概念
      多节点分布式,同时执行或者回滚某个事务性(全部执行或全部不执行)的操作;
      协作者:引入中心节点统一处理节点的执行逻辑;
      参与者:被中心节点调度的其他业务。
    • 预提交阶段
      1. 协作者向参与者发送commit消息
      2. 参与者执行事务但不提交
      3. 执行成功与否,vote yes/no
    • 提交阶段
      • 所有vote yes
        1. 协作者发送commit;
        2. 参与者释放所需资源,结束事务执行;
        3. 发送ack消息给协作者;
        4. 协作者收到所有ack,标识事务执行完成。
      • 有一个部分vote no
        1. 协作者向所有参与者发送rollback;
        2. 参与者收到rollback,回滚执行操作,并释放事务所占资源;
        3. 参与者发送ack给协作者;
        4. 协作者收到所有ack,标识事务完成回滚。
  • Flink中2PC Sink
    协作者:JobManager
    参与者:eg data source,sink data
    Kafka:消息队列;二维数组;数组无限长;有限;从队列头读,队列尾新增
    • pre commit
      1. checkpoint starts
        Job manager 发布checkpoint barrier
      2. without external state
        传递barrier,状态保存到state backend
      3. with external state in data sink
        传递barrier,更新状态保存到state backend,传递给kafka,但kafka新增数据不可见
    • commit
      1. JM 发布给所有参与者 checkpoint completed
      2. sink向kafka commit 数据
  • Conclusion
    • 事务开启:sink task写入数据之前,开启事务,在事务中执行,未提交前所有数据不可读。
    • 预提交阶段:JM发布checkpoint barrier,制作快照,sink不再继续处理数据(除非新开下一个事物),状态成功向JM提交消息。
    • 提交阶段:所有成功消息后,向所有处理逻辑(含sink)发送可以提交此事务消息,sink完成提交,下游可写入数据;若收到失败,则通知所有处理逻辑回滚事务,sink丢弃此次数据。
  • 缺点:延时高,在批处理或者流处理较小情况下应用

4. 案例讲解

  • 账单计算服务
    1. 在上次记录位点之后,从Kafka(存在重复消息 at-least-once)读取固定大小的数据
    2. 批处理去重&融合
    3. 处理完成写入Mysql,成功记录终止为止,失败不记录位点
  • 问题:
    1. 非严格意义的端到端Exactly-Once语义:
      存在部分数据写入,可能在下次启动时数据重复写入,但Mysql可以避免重复写入
    2. 去重能力有限:
      只能在一批数据内去重,无法在批与批之间去重
  • 解决方案:Flink
    从Kafka读取账单消息,进行处理后写入到MySQL
    1. 严格意义的端到端Exactly-Once语义
    2. 增强的去重能力(更长时间维度):定时删除数据,设置保留时间

Conclusion

  1. 学习整体Flink中的处理逻辑,依靠动态表和数据流的相互转换
  2. 学习Exactly-once中算子通过barrier和JM相互交流实现
  3. 学习两阶段提交协议在Flink中实现Exactly-once,通过kafka的预处理和处理阶段中协作者与参与者的交流,保证数据输出可靠性
  4. 学习Flink的端到端的Exactly-once的实例应用