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

44 阅读7分钟

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

一、数据流和动态表

  • 动态表:是随时间变化的,可以像查询静态批处理表一样查询它们
    • 查询类型一:查询后会更新先前输出的结果 (定义结果表的 changelog 流包含 INSERT 和 UPDATE 操作)
    • 查询类型二:查询后只在结果表附加内容 (定义结果表的 changelog 流只包含 INSERT 操作)

image.png(查询类型一)

image.png(查询类型二)

  • 实时流查询(连续查询)

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

    • Append-only Stream: Append-only 流(只有 INSERT 消息)
    • Retract Stream: Retract 流(同时包含 INSERT 消息和 DELETE 消息)
    • Upsert Stream: Upsert 流(同时包含 UPSERT 消息和 DELETE 消息)

image.png

image.png

  • 算子状态
    • 算子,即计算逻辑。在流式计算中,算子具有状态

    • 有状态的算子的处理逻辑:

      接收输入-->获取算子状态-->将二者进行计算后更新算子状态-->发出计算结果

image.png

(比如,需要计算某个用户在网上的点击量,该用户在网站当前的总点击次数就是算子状态,对于新的输入数据,先判断是否是该用户的点击行为,如果是,则将保留的点击次数(状态)增加一,并将当前累加结果输出。)
  • 数据流和动态表的转换关系

image.png

二、Exactly-Once 和 Checkpoint

一致性保证语义

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

Checkpoint

状态快照与恢复

发生故障时,可根据 storage 里存储的各算子状态进行恢复。

image.png

  • 状态恢复时间点:

    等待所有处理逻辑(算子)消费完成 source 保留状态及之前 的数据。(把手头的工作结清)

  • 一个简单的快照制作算法

    • 暂停处理输入的数据
    • 等待后续所有算子消费当前已输入的数据
    • 处理完后,所有算子复制自己的状态并保存到远端可靠存储
    • 恢复对输入数据的处理

Chandy-Lamport 算法

以一个并行处理的例子来说明。Sum even 计算偶数累加和,Sum odd 计算奇数累加和。 image.png

  • 快照制作开始

    JobManager 负责向下游 source 发送 Checkpoint Barrier 将数据隔开,分成数据段。同时也是快照制作的开始,标志故障恢复时间点。(三角形的barrier加入圆形的数据一起流动) image.png

  • Source 算子处理

    Source 收到 Checkpoint Barrier 后,暂停处理逻辑,同时将当前状态保存。接着向所有下游(Sum even 和 Sum odd)发送 Checkpoint Barrier(本例中一个变两个)。发送后可继续处理数据,不考虑下游情况。 image.png

  • Barrier Alignment

    对于 Sum even 和 Sum odd,会收到来自 source 1 和 source 2 的两个 Checkpoint Barrier,而到达的时间不能完全一致,存在先后顺序。算子要等待上游所有的 Checkpoint Barrier 到达之后才能完成状态制作和保存。

    假如 source 1 的 Checkpoint Barrier 先到了 Sun even,后续的数据也来到,先将数据缓存不处理,直到 source 2 的 Checkpoint Barrier 也来到 Sun even,完成状态制作和保存,向下游发送 Checkpoint Barrier,才恢复处理数据。 image.png

  • 快照制作和处理数据的解耦

    sink 的流程类似前面。因为没有下游,所以 Checkpoint Barrier 不再往下传递。 image.png image.png

  • Checkpoint 的结束

    所有算子都通知 JM 状态制作完成后,整个 Checkpoint 就结束了。 image.png

Checkpoint 对作业性能的影响

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

三、Flink 端到端的 Exactly-Once 语义

端到端 Exactly-Once 语义

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

两阶段提交协议(2PC)

在多个结点参与的分布式系统中,为了协调每个结点都能同时执行或回滚某个事务性的操作,引入了一个中心结点来统一处理所有结点的执行逻辑。

  • Coordinator:协作者,同步和协调所有节点处理逻辑的中心节点
  • Participant:参与者,被中心节点调度的其他执行处理逻辑的业务节点

预提交阶段

  1. 协作者向所有参与者发送一个 commit 消息
  2. 每个参与者收到消息后执行事务,但不真正提交
  3. 参与者向协作者发送执行成功(vote yes)或失败(vote no)的消息

提交阶段

  • 若协作者成功接收到所有的参与者 vote yes 的消息,
    1. 协作者向所有参与者发送一个 commit 消息;
    2. 每个收到 commit 消息的参与者释放执行事务所需的资源,并结束这次事务的执行;
    3. 完成步骤2后,参与者发送一个 ack 消息给协作者;
    4. 协作者收到所有参与者的 ack 消息后,标识该事务执行完成。
  • 若协作者收到参与者 vote no 的消息(或者发生等待超时),
    1. 协作者向所有参与者发送一个 rollback 消息;
    2. 每个收到 rollback 消息的参与者回滚事务的执行操作,并释放事务所占资源;
    3. 完成步骤 2 后,参与者发送一个 ack 消息给协作者;
    4. 协作者收到所有参与者的 ack 消息后,标识该事务成功完成回滚。

image.png

两阶段提交协议在 Flink 中的应用

  • Flink 中协作者和参与者的角色分配 image.png

  • 协作者(JobManager)发起阶段一提交 image.png

  • 各算子 Checkpoint 的制作 image.png

  • 提交阶段及 Checkpoint 的制作完成 image.png

四、案例讲解

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

  • 执行步骤:

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

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

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

总结

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