Flink中的状态一致性

1.概念

一致性其实就是结果的正确性。对于分布式系统而言,从不同节点读取时总能得到相同的值;而对于事务而言,是要求提交更新操作后,能够读取到新的数据。

  • 有状态的流处理,内部每个算子任务都可以有自己的状态。
  • 对于流处理内部来说,所谓状态一致性,其实就是所说的计算结果要保证准确。
  • 一条数据不应该丢失,也不应该重复计算。
  • 在遇到故障时可以状态恢复,恢复以后重新计算,计算结果也是完全正确的。

2.一致性级别

  • AT-MOST-ONCE(最多一次)
  • 当任务发生故障时,直接重启,别的什么都不干;既不恢复丢失的状态,也不重放丢失的数据。每条数据在正常情况下会被处理一次,遇到故障时就会丢掉。即为每条消息就只消费一次。
  • AT-LEAST-ONCE(至少一次)
    • 所有的数据都不会丢,肯定被处理了;不过不能保证只处理一次,有些数据会被重复处理。在某些场景下,重复处理数据是不会影响结果的正确性。比如:统计某电商网站的UV。
  • EXACTLY-ONCE(精确一次
    • 所有数据不仅不会丢失,而且只会被处理一次,不会重复处理。Flink中使用的是一种轻量级快照机制---检查点(checkpoint)来保证exactly-once语义。

3.端到端精确一次(end-to-end exactly-once)

对于Flink内部来说,检查点机制可以保证故障恢复之后数据不丢(在能够重复放的情况下),并且只处理一次,所以已经可以做到exactly-once的一致性语义了。故端到端一致性的关键点,就在于输入的数据源端和输出的外部存储端。

3.1 输入端保证

输入端主要指的就是Flink读取的外部数据源。想要在故障恢复后不丢数据,外部数据源就必须拥有重放数据的能力。常见做法就是对数据进行持久化保存,并且可以重设数据的读取位置。一个最经典的应用就是Kafka。在Flink的Source任务中将数据读取的偏移量保存为状态,这样就可以在故障恢复时从检查点中读取出来,对数据源重置偏移量,重新获取数据。

3.2 输出端保证

输出端主要指的就是Flink处理完数据,通过sink任务输出到外部系统。对于输出端,需要保证在故障恢复时,数据不会重复写入外部系统。对于输出端,有两种 具体的实现方式:幂(Idempotent)写入和事务性(Transactional)写入。

3.2.1 幂等(idempotent)写入

所谓"幂等"操作,就是说一个操作可以重复执行很多次,但只导致一次结果更改。这种方式主要限制在于外部存储系统必须支持这样的幂等写入。

对于幂等写入,在遇到故障恢复时,可能会出现短暂的不一致。因为保存点完成之后到发生故障之间的数据,其实已经写入了一遍,回滚的时候并不能消除它们。不过当数据的重放逐渐超过发生故障的点的时候,最终的结果还是一致的。

3.2.2 事务(transaction)写入

输出端最大的问题,就是写入到外部系统的数据难以撤回。而利用事务就可以实现对已写入数据的撤回。在Flink流处理的结果写入外部系统时,如果能够构建一个事务,让写入操作可以随着检查点来提交和回滚,这样就解决了重复写入的问题。 事务与检查点是绑定在一起的,当Sink任务遇到barrier时,开始保存状态的同时就开启一个事务,后续所有数据的写入都在该事务中;待到当前检查点保存完毕时,将事务提交,所有写入的数据就真正可用了。如果中间过程出现故障,状态会回退到上一个检查点,而当前事务没有正常关闭(因为当前检查点没有保存完),所以也会回滚,写入到外部的数据就被撤销了。对于事务写入有两种实现方式:预写日志(WAL)和两阶段提交(2PC)。

预写日志(write-ahead-log,WAL)

实现步骤:

  1. 先把结果数据作为日志(log)状态保存起来;
  2. 进行检查点保存时,同时将这些结果数据做持久化存储;
  3. 在收到检查点完成的通知时,将所有结果一次性写入外部系统。

预写日志这种批写入的方式,有可能会写入失败;所以在写入动作之后,必须等待发送成功的返回确认消息。在成功写入所有数据后,内部再次确认相应的检查点,此时代表着检查点的真正完成。同时也需要确认信息也进行持久化保存,在故障恢复时,只有存在对应的确认信息,才能保证这批数据已经写入,可以恢复到对应的检查点位置。

两阶段提交(two-phase-commit,2PC)

两阶段提交,顾名思义,先做"预提交",等检查点完成之后再正式提交。这种提交方式是真正基于事务的,它需要外部系统提供事务支持。

实现步骤:

  1. 当第一条数据到来时,或者收到检查点的分界线时,Sink任务都会启动一个事务。
  2. 接下来接收到的所有数据,都通过这个事务写入外部系统;这时由于事务没有提交,数据虽然写入了外部系统,但是不可用,是"预提交"的状态。
  3. 当Sink任务收到JobManager发来检查点完成的通知时,正式提交事务,写入的结果就真正可用了。

当中间发生故障时,当前未提交的事务就会回滚,于是所有写入外部系统的数据也就实现了撤回。两阶段提交的方式充分利用了Flink现有的检查点机制:分界线的到来,就标志着开始一个新事务;而收到来自JobManager的checkpoint成功的消息,就是提交事务的指令。

两阶段提交对外部系统有要求:

  • 外部系统必须提供事务支持,或者Sink任务必须能够模拟外部系统上的事务。
  • 在检查点的间隔期间里,必须能够开启一个事务并接受数据写入。
  • 在收到checkpoint完成的通知之前,事务必须是"等待提交"的状态。在故障恢复的情况下,这可能需要-些时间。如果这个时候sink系统关闭事务(例如超时了) ,那么未提交的数据就会丢失
  • sink任务必须能够在进程失败后恢复事务 。
  • 提交事务必须是幂等操作。

3.3 总结

对于端到端的精准一次而言:

系统 要求
source端 可重设数据的读取位置
Flink内部 依赖checkpoint
sink端 从故障恢复时,数据不会重复写入外部系统

4.Flink和Kafka连接时的精确一次保证

4.1 概述

Flink与Kafka连接时属于端到端精准一次,可以从三个组件的角度进行分析:

  • Flink内部
    • 通过checkpoint机制, 把状态存盘,发生故障的时候可以恢复,保证内部的状态一致性。
  • 输入端
    • 输入数据源端的Kafka可以对数据进行持久化保存,并可以重置偏移量(offset)。在Source任务(FlinkKafkaConsumer)中将当前读取的偏移量保存为算子状态,写入到检查点中;当发生故障时,从检查点中读取恢复状态,并由连接器FlinkKafkaConsumer向Kafka重新提交偏移量,就可以重新消费数据、保证结果的一致性了。
  • 输出端
    • 输出端保证exactly-once的最佳实现,当然就是两阶段提交(2PC)。Flink官方实现的Kafka连接器中,提供了写入到Kafka的FlinkKafkaProducer,它就实现TwoPhaseCommitSinkFunction接口。
    • 两阶段提交:处理完毕得到结果,写入Kafka时是基于事务的"预提交";等到检查点保存完毕,才会提交事务进行"正式提交"。

4.2 2PC具体步骤

情景: 由Flink从Kafka读取数据、并将处理结果写入Kafka,如图所示

Flink与Kafka连接的两阶段提交,离不开检查点的配合,这个过程需要JobManager协调各个TaskManager进行状态快照,而检查点具体存储位置则是由状态后端(State Backend)来配置管理的。一般情况,我们会将检查点存储到分布式文件系统上。

(1) 启动检查点保存

检查点保存的启动,标志着进入了两阶段提交协议的"预提交"阶段。当然,现在还没有具体提交的数据。

JobManager通知各个TaskManager启动检查点保存,Source任务会将检查点分界线(barrier)注入数据流。这个barrier可以将数据流中的数据,分为进入当前检查点的集合和进入下一个检查点的集合。

(2) 算子任务对状态做快照

分界线(barrier)会在算子间传递下去。每个算子收到barrier时,会将当前的状态做个快照,保存到状态后端。

Source任务将barrier插入数据流后,也会将当前读取数据的偏移量作为状态写入检查点,存入状态后端;然后把barrier向下游传递,自己就可以继续读取数据了。接下来barrier传递到了内部的Window算子,它同样会对自己的状态进行快照保存,写入远程的持久化存储。

(3) Sink任务开启事务,进行预提交

分界线(barrier)终于传到了Sink任务,这时Sink任务会开启一个事务。后续到来的所有数据,Sink任务都会通过该事务来写入Kafka。这里barrier是检查点的分界线,也是事务的分界线。由于之前的检查点可能尚未完成,因此上一个事务也可能尚未提交;此时barrier的到来开启了新的事务,上一个事务尽管可能没有被提交,但也不再接收新的数据了。

对于Kafka而言,提交的数据会被标记为"未确认"(uncommitted)。这个过程就是所谓的"预提交"(pre-commit)。

(4) 检查点保存完成,提交事务

当所有算子的快照都完成,也就是这次的检查点保存最终完成时,JobManager会向所有任务发确认通知,告诉TaskManager当前检查点已成功保存。

当Sink任务收到确认通知后,就会正式提交之前的事务,把之前"未确认"的数据标为"已确认",接下来就可以正常消费了。

4.3 需要的配置

  • 必须启用checkpoint。
  • 在FlinkKafkaProducer的构造函数中传入参数Semantic.EXACTLY_ONCE。
  • 配置Kafka(写入的外部系统)读取数据的消费者的隔离级别:isolation.level=read_commited。
  • 事务超时配置:Flink的Kafka连接器中配置的事务超时时间transaction.timeout.ms默认是1小时,而Kafka集群配置的事务最大超时时间transaction.max.timeout.ms默认是15分钟。实际配置应该是:transaction.timeout.ms≤transaction.max.timeout.ms。
相关推荐
异世界贤狼转生码农1 小时前
MongoDB Windows 系统实战手册:从配置到数据处理入门
数据库·mongodb
QuZhengRong1 小时前
【数据库】Navicat 导入 Excel 数据乱码问题的解决方法
android·数据库·excel
码农阿豪1 小时前
Windows从零到一安装KingbaseES数据库及使用ksql工具连接全指南
数据库·windows
时序数据说7 小时前
时序数据库市场前景分析
大数据·数据库·物联网·开源·时序数据库
听雪楼主.10 小时前
Oracle Undo Tablespace 使用率暴涨案例分析
数据库·oracle·架构
我科绝伦(Huanhuan Zhou)10 小时前
KINGBASE集群日常维护管理命令总结
数据库·database
妖灵翎幺11 小时前
Java应届生求职八股(2)---Mysql篇
数据库·mysql
HMBBLOVEPDX11 小时前
MySQL的事务日志:
数据库·mysql
YA33312 小时前
java基础(九)sql基础及索引
java·开发语言·sql
weixin_4196583113 小时前
MySQL数据库备份与恢复
数据库·mysql