Exactly Once 语义在 Flink中的实现 | 青训营笔记

123 阅读7分钟

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

1、数据流和动态表


(1)传统 SQL 和流处理

image.png

  • 有界:数据在一定程度上固定
  • 流:数据源源不断改变、增减

(2)数据流和动态表转换

image.png

graph LR
流 --> 动态表 --> 连续查询+状态 --> 动态表 --> 流

(3)在流上定义表(Dynamic Table)

  • 下图单击事件流(左侧)如何转换为表(右侧),当插入更多的单击流记录时,结果表将不断增长 image.png
  • 流 ——> 表
  • Dynamic Table(动态表):与表示批处理数据的静态表不同,动态表是随时间变化的;可以像查询静态批处理表一样查询他们。
  • 数据库表:是 INSERT、UPDATE 和 DELETE DML 语句的 stream 的结果通常称为 changelog stream。

(4)连续查询

image.png

  • 动态表 ——> 动态表
  • 查询从不终止
  • 查询结果会不断更新,产生一个新的动态表
  • 在任何时候,连续查询的结果在语义上与以批处理模式在输入表快照上执行的相同查询的结果相同

(5)查询产生仅追加数据(即定义时间内的记录)的动态表

image.png

(6)两个连续查询对比

image.png image.png

  • 虽然这两个实例查询看起来非常相似(都计算分组计数聚合),但它们在一个重要方面不同:
    • 第一个查询:更新先前输出的结果,即定义结果表的 changelog 流包含 INSERT 和 UPDATE 操作
    • 第二个查询:只附加到结果表,即结果表的 changelog 流只包含 INSERT 操作。

(7)Retract (回溯)消息的产生(以 Mary为例)

image.png

(8)状态

image.png

  • 需要存储每个用户的 url 计数,以便能够增加该计数并在输入表接收新行时发送新结果

(9)数据流和动态表转换回顾

image.png

(10)不同数据保证的语义

  • At-most-once:出现故障时,啥也不做。数据处理不保证任何语义,处理时延低;
  • At-least-once:保证每条数据均至少被处理一次,一条数据可能存在重复消费;
  • Exactly-once:最严格的处理语义,从输出结果来看,每条数据均被消费且仅消费一次,仿佛故障从未发生。

2、Exactly-Once 和 Checkpoint


(1)状态快照与恢复

image.png

  • 设置故障恢复的时间点(例如计算机定时快照备份),一旦故障发生将回到此时刻并恢复此时的状态
  • 三个算子:
    • Source —> 负责读取数据流
    • sum_even —> 进行偶数累加
    • sum_odd —> 进行奇数累加 image.png

(2)制作快照的时间点

image.png

  • 状态恢复的时间点:需要等待所有处理逻辑消费完成 source 保留状态及之前的数据
  • 一个简单的快照制作算法
    • 1.暂停处理输入的数据
    • 2.等待后续所有处理算子消费当前已经输入的数据
    • 3.待 2 处理完后,作业所有算子复制自己的状态并保存到远端可靠存储
    • 4.恢复对输入数据的处理

(3)Chandy-Lamport 算法

image.png

  • 制作快照的开始 image.png

    • 每个 source 算子都接收到 JM 发送的 Checkpoint Barrier 标识状态快照制作的开始
  • Source 算子的处理 image.png

    • 各个 source 保存自己的状态后,向所有连接的下游继续发送 Checkpoint Barrier,同时告知 JM 自己状态已经制作完成
  • Barrier Alignment image.png

    • 算子会等待所有上游的 Barrier 到达后才开始制作快照
    • 已经制作完成的上游算子会继续处理数据,并不会被下游算子制作快照的过程阻塞
  • 快照制作和处理数据的解耦 image.png image.png

  • Checkpoint 的结束 image.png

    • 所有算子都告知 JM 状态制作完成后,整个 Checkpoint 就结束了。
  • Checkpoint 对作业性能的影响

    • 解耦了快照制作和数据处理过程,各个算子制作完成状态快照后就可以正常处理数据,不用等下游算子制作完成快照
    • 在快照制作和 Barrier Alignment 过程中需要暂停处理数据,仍然会增加数据处理延迟
    • 快照保存到远端也可能会极为耗时

3、端到端 Exactly-Once 实现


(1)端到端 Exactly-Once 语义

image.png

  • Checkpoint 能保证每条数据都对各个有状态的算子更新一次,sink输出算子仍然可能下发重复的数据
  • 严格意义的端到端的 Exactly-once 语义需要特殊的 sink 算子实现

(2)两阶段提交协议

image.png 在多个节点参与执行的分布式系统中,为了协调每个节点能能同时回滚某个事务性的操作,引入了一个中心节点来统一处理所有节点的执行逻辑,这个中心节点叫做协作者(coordinator),被中心节点调度的其他业务节点叫做参与者(participant)

  • 1.预备提交阶段
    • 1.协作者向所有参与者发送一个 commit 消息
    • 2.每个参与的协作者收到消息后,执行事务,但是不真正提交
    • 若事务成功执行完成,发送一个成功的消息(vote yes);执行失败,则发送一个失败的消息(vote no)
  • 2.提交阶段
    • 若协作者成功收到所有参与者 vote yes 的消息:
      • 协作者向所有参与者发送一个 commit 消息
      • 每个收到 commit 消息的参与者释放执行事务所需的资源,并结束这次事务的执行
      • 完成 2 后,参与者发送一个 ack 消息给协作者
      • 协作者收到所有参与者的 ack 消息后,标识该事务所执行完成
    • 若协作者有收到参与者 vote no 的消息(或者发生等待超时)
      • 协作者向所有参与者发送一个 roolback 消息
      • 每个收到 roll back 消息的参与者回滚事务的执行操作,并释放事务所占资源
      • 完成 2 后,参与者发送一个 ack 消息给协作者
      • 协作者收到所有参与者的 ack 消息后,标识该事务的成功完成回滚

(3)Flink 中 2PC Sink

image.png image.png image.png image.png

(4)Flink 两阶段提交总结

image.png

  • 1.事务开启:在 sink task 向下游写数据之前,均会开启一个事务,后续所有写数据的操作均会在这个事务中执行,事务未提交之前,事务写入的数据下游不可读;
  • 2.预提交阶段:JobManager 开始下发 CheckPoint,当各个处理逻辑接收到 barrier 后停止处理后续数据,对当前状态制作快照,此时 sink 也不在当前事务下继续处理数据(处理后续的数据需要新打开下一个事务),状态制作成功则向 JM 成功的消息,失败则发送失败的消息;
  • 3.提交阶段:若 JM 收到所有预提交成功的消息,则向所有处理逻辑(包括 sink)发送可以提交此次事务的消息,sink 接收到此消息后,则完成此次事务的提交,此时下游可以读到这次事务写入的数据;若 JM 有收到预提交失败的消息,则通知所有处理逻辑回滚这次事务的操作,此时 sink 则丢弃这次事务提交的数据下游。

4、Flink 案例讲解

账单计算服务

  • 从 kafka 中读取账单消息,进行处理后写入到 MySQL 中 image.png

  • 执行步骤

    • 1.在上次记录的位点之后,从 Kafka 中读取固定的大小的数据;
    • 2.对该批数据进行去重和聚合计算;
    • 3.处理完成后写入 MyySQL 中,若全部写入成功,则记录下当前读取到的消息的终止位置;若处理和写入失败,则不记录位点;
    • Return 1。
  • 存在的问题

    • 非严格意义上的端到端的 Exactly-once 语义:
      • 若该批数据处理完后,再写入 MySQL 中发生异常,则存在部分数据写入的情况,下次作业启动后,这部分数据仍然会重复写入;
      • 去重能力有限:只能在当前处理的一批数据内进行去重,无法批与批之间机进行去重。
  • Flink 处理方案 image.png

    • 优势
      • 严格意义上的端到端的 Exactly-once 语义:下游读到的数据是不丢不重的;
      • 增强的去重能力:可以在更长的时间维度对数据进行去重。

5、总结


  • 数据流可以转换为动态表,动态表也能重新转换为数据流
  • 处理无限数据流的算子可以是有状态的
  • Flink 通过 Checkpoint 机制实现故障前后的状态快照制作和恢复
  • 支持两阶段提交协议的下游存储可以结合 Flink Checkpoint 机制实现严格意义上端到端的 Exactly-one 语义实现