flink系列-10、flink保证数据的一致性
本文摘自书籍《Flink基础教程》
一、一致性的三种级别
当在分布式系统中引入状态时,自然也引入了一致性问题。一致性实际上是“正确性级别”的另一种说法,即在成功处理故障并恢复之后得到的结果,与没有发生任何故障时得到的结果相比。在流处理中,一致性分为 3 个级别。
- at-most-once:数据最多被处理一次。这其实是没有正确性保障的委婉说法——故障发生之后,计数结果可能丢失。
- at-least-once:数据最少被处理一次。这表示计数结果可能大于正确值,但绝不会小于正确值。也就是说,计数程序在发生故障后可能多算,但是绝不会少
- exactly-once:数据只被处理一次(最优)。这指的是系统保证在发生故障后得到的计数结果与正确值一致。
曾经, at-least-once 非常流行。第一代流处理器(如 Storm 和 Samza)刚问世时只保证 at-least-once,原因有二:
- 保证 exactly-once 的系统实现起来更复杂。这在基础架构层(决定什么代表正确,以及 exactly-once 的范围是什么)和实现层都很有挑战性。
- 流处理系统的早期用户愿意接受框架的局限性,并在应用层想办法弥补(例如使应用程序具有幂等性,或者用批量计算层再做一遍计算)。
最先保证 exactly-once 的系统(Storm Trident 和 Spark Streaming)在性能和表现力这两个方面付出了很大的代价:
- 为了保证 exactly-once,这些系统无法单独地对每条记录运用应用逻辑,而是同时处理多条(一批)记录,保证对每一批的处理要么全部成功,要么全部失败。
- 这就导致在得到结果前,必须等待一批记录处理结束。
- Flink 的一个重大价值在于,它既保证了 exactly-once,也具有低延迟和高吞吐的处理能力。
二、检查点,保证exactly-once
- Flink使用一种被称为“检查点”的特性,在出现故障时将系统重置回正确状态。
- 检查点是 Flink 最有价值的创新之一,因为它使 Flink 可以保证 exactly-once,并且不需要牺牲性能。
- Flink 检查点的核心作用是确保状态正确,即使遇到程序中断,也要正确。记住这一基本点之后,我们用一个例子来看检查点是如何运行的。Flink 为用户提供了用来定义状态的工具。
- 例如,以下这个 Scala 程序按照输入记录的第一个字段(一个字符串)进行分组并维护第二个字段的计数状态。
val mapData: DataStream[(String, Int)] = dataSource.flatMap(f => f.split(" ")).map((_, 1))
val mapWithStageData: DataStream[(String, Int)] = mapData.keyBy(_._1).mapWithState((in: (String, Int), count: Option[Int]) => {
count match {
case Some(c) => ((in._1, c + in._2), Some(c + in._2))
case None => ((in._1, in._2), Some(in._2))
}
})
该程序有两个算子:keyBy 算子用来将记录按照第一个元素(一个字符串)进行分组,根据该 key 将数据进行重新分区,然后将记录再发送给下一个算子:有状态的 map 算子(mapWithState)。map算子在接收到每个元素后,将输入记录的第二个字段的数据加到现有总数中,再将更新过的元素发射出去。
上图表示程序的初始状态:输入流中的6条记录被检查点屏障(checkpoint barrier)隔开,所有的map算子状态均为0(计数还未开始)。所有key为a的记录将被顶层的map算子处理,所有key为b的记录将被中间层的map算子处理,所有key为c的记录则将被底层的map算子处理。其中 ["b",2] 在检查点之前被处理, ["a",2] 则在检查点之后被处理
当该程序处理输入流中的 6 条记录时,涉及的操作遍布 3 个并行实例(节点、 CPU 内核等)。那么,检查点该如何保证 exactly-once 呢?检查点屏障和普通记录类似。它们由算子处理,但并不参与计算,而是会触发与检查点相关的行为。当读取输入流的数据源(在本例中与 keyBy 算子内联)遇到检查点屏障时,它将其在输入流中的位置保存到稳定存储中。如果输入流来自消息传输系统(Kafka 或 MapR Streams),这个位置就是偏移量。 Flink 的存储机制是插件化的,稳定存储可以是分布式文件系统,如HDFS、 S3 或 MapR-FS。下图展示了这个过程。
如下图所示:位于检查点之前的所有记录(["b",2]、 ["b",3] 和 ["c",1])被 map 算子处理之后的情况。此时,稳定存储已经备份了检查点屏障在输入流中的位置(备份操作发生在检查点屏障被输入算子处理的时候)。 map 算子接着开始处理检查点屏障,并触发将状态异步备份到稳定存储中这个动作。检查点屏障像普通记录一样在算子之间流动。当 map 算子处理完前 3 条记录并收到检查点屏障时,它们会将状态以异步的方式写入稳定存储。
当 map 算子的状态备份和检查点屏障的位置备份被确认之后,该检查点操作就可以被标记为完成,如下图所示。我们在无须停止或者阻断计算的条件下,在一个逻辑时间点(对应检查点屏障在输入流中的位置)为计算状态拍了快照。通过确保备份的状态和位置指向同一个逻辑时间点,从而保证 exactly-once。值得注意的是,当没有出现故障时, Flink 检查点的开销极小,检查点操作的速度由稳定存储的可用带宽决定。值得注意的是,备份的状态值与实际的状态值是不同的。备份反映的是检查点的状态
如果检查点操作失败, Flink 会丢弃该检查点并继续正常执行,因为之后的某一个检查点可能会成功。虽然恢复时间可能更长,但是对于状态的保证依旧很有力。只有在一系列连续的检查点操作失败之后, Flink 才会抛出错误,因为这通常预示着发生了严重且持久的错误。
在这种情况下, Flink 会重新拓扑(可能会获取新的执行资源),将输入流倒回到上一个检查点,然后恢复状态值并从该处开始继续计算。
二、保存点: 状态版本控制
检查点由 Flink 自动生成,用来在故障发生时重新处理记录,从而修正状态。 Flink 用户还可以通过另一个特性有意识地管理状态版本,这个特性叫作保存点(savepoint)
- 保存点与检查点的工作方式完全相同,只不过它由用户通过 Flink 命令行工具或者 Web 控制台手动触发,而不由 Flink 自动触发。
- 和检查点一样,保存点也被保存在稳定存储中。
- 用户可以从保存点重启作业,而不用从头开始。
- 保存点可以被视为作业在某一个特定时间点的快照(该时间点即为保存点被触发的时间点)。
- 对保存点的另一种理解是,它在明确的时间点保存应用程序状态的版本。这和用版本控制系统保存应用程序的版本很相似。最简单的例子是在不修改应用程序代码的情况下,每隔固定的时间拍快照,即照原样保存应用程序状态的版本。
上图中, v.0 是某应用程序的一个正在运行的版本。我们分别在 t1 时刻和 t2时刻触发了保存点。因此,可以在任何时候返回到这两个时间点,并且重启程序。更重要的是,可以从保存点启动被修改过的程序版本。举例来说,可以修改应用程序的代码(假设称新版本为 v.1),然后从 t1 时刻开始运行改动过的代码。这样一来, v.0 和 v.1 这两个版本同时运行,并在之后的时间里获取各自的保存点。
保存点可用于应对流处理作业在生产环境中遇到的许多挑战:
- 应用程序代码升级:假设你在已经处于运行状态的应用程序中发现了一个 bug,并且希望之后的事件都可以用修复后的新版本来处理。通过触发保存点并从该保存点处运行新版本,下游的应用程序并不会察觉到不同(当然,被更新的部分除外)。
- Flink 版本更新: Flink 自身的更新也变得简单,因为可以针对正在运行的任务触发保存点,并从保存点处用新版本的 Flink 重启任务。
- 维护和迁移:使用保存点,可以轻松地“暂停和恢复”应用程序。这对于集群维护以及向新集群迁移的作业来说尤其有用。此外,它还有利于开发、测试和调试,因为不需要重播整个事件流。
- 假设模拟与恢复:在可控的点上运行其他的应用逻辑,以模拟假设的场景,这样做在很多时候非常有用。
- A/B 测试:从同一个保存点开始,并行地运行应用程序的两个版本,有助于进行 A/B 测试
三、端到端的一致性和作为数据库的流处理器
我们已经通过简单的计数例子了解了 Flink 如何保证状态的一致性(即保证exactly-once)。接下来看看端到端的情况,因为在生产环境中可能会部署这种应用程序。
下面的应用程序架构中,有状态的 Flink 应用程序消费来自消息队列的数据,然后将数据写入输出系统,以供查询。底部的详情图展示了 Flink 应用程序的内部情况。
输入数据来自一个分区存储系统(如 Kafka 或者 MapR Streams 这样的消息队列)。底部的详情图展示了 Flink 拓扑,其中包含 3 个算子。source 读取输入数据,根据 key 分区,并将数据路由到有状态的算子实例。有状态的算子将状态内容(比如前例中的计数结果)或者一些衍生结果写入 sink,再由 sink 将结果传送到输出存储系统中(例如文件系统或数据库)。接着,查询服务(比如数据库查询 API)就可以允许用户对状态进行查询(最简单的例子就是查询计数结果),因为状态已经被写入输出存储系统了。
需要记住的是,在本例中,输出反映的是截至最近一次写入状态之时, Flink 应用程序中的状态内容。
在将状态内容传送到输出存储系统的过程中,如何保证 exactly-once 呢?这叫作端到端的一致性。本质上有两种实现方法,用哪一种方法则取决于输出存储系统的类型,以及应用程序的需求:
- 第一种方法是在 sink 环节缓冲所有输出,并在 sink 收到检查点记录时,将输出“原子提交”到存储系统。这种方法保证输出存储系统中只存在有一致性保障的结果,并且不会出现重复的数据。从本质上说,输出存储系统会参与 Flink 的检查点操作。要做到这一点,输出存储系统需要具备“原子提交”的能力。
- 第二种方法是急切地将数据写入输出存储系统,同时牢记这些数据可能是“脏”的,而且需要在发生故障时重新处理。如果发生故障,就需要将输出、输入和 Flink 作业全部回滚,从而将“脏”数据覆盖,并将已经写入输出的“脏”数据删除。注意,在很多情况下,其实并没有发生删除操作。例如,如果新记录只是覆盖旧纪录(而不是添加到输出中),那么“脏”数据只在检查点之间短暂存在,并且最终会被修正过的新数据覆盖。
值得注意的是,这两种方法恰好对应关系数据库系统中的两种为人所熟知的事务隔离级别: 已提交读(read committed)和未提交读(read uncommitted)。已提交读保证所有读取(查询输出)都只读取已提交的数据,而不会读取中间、传输中或“脏”的数据。之后的读取可能会返回不同的结果,因为数据可能已被改变。未提交读则允许读取“脏”数据;换句话说,查询总是看到被处理过的最新版本的数据。
某些应用程序可以接受弱一点的语义,所以 Flink 提供了支持多重语义的多种内置输出算子,如支持未提交读语义的分布式文件输出算子。用户可以根据输出存储系统的能力和应用程序的需求选择合适的语义。根据输出存储系统的类型, Flink 及与之对应的连接器可以一起保证端到端的一致性,并且支持多种隔离级别。
上面的应用程序架构。之所以需要有输出存储系统,是因为外部无法访问 Flink 的内部状态,所以输出存储系统成了查询目标。但是,如果可以直接查询状态,则在某些情况下根本就不需要输出存储系统,因为状态本身就已经包含了查询所需的信息。这种情况在许多应用程序中真实存在, 直接查询状态可以大大地简化架构,同时大幅提升性能
flink系列-10、flink保证数据的一致性的更多相关文章
- Cassandra如何保证数据最终一致性
Cassandra如何保证数据最终一致性:1.逆熵机制(Anti-Entropy)使用默克尔树(Merkle Tree)来确认多个副本数据一致,对于不一致数据,根据时间戳来获取最新数据. 2.读修复机 ...
- 代码中添加事务控制 VS(数据库存储过程+事务) 保证数据的完整性与一致性
做人事档案的系统考虑到数据的安全性与一致性,毕竟是要对外上线.真正投入使用的项目,数据库的可靠性与安全性上我们开发人员要考虑的就很多了,记得做机房收费系统时注册新卡是自己为了简单,写成了一个存储过程( ...
- Flink系列之1.10版流式SQL应用
随着Flink 1.10的发布,对SQL的支持也非常强大.Flink 还提供了 MySql, Hive,ES, Kafka等连接器Connector,所以使用起来非常方便. 接下来咱们针对构建流式SQ ...
- Flink系列(0)——准备篇(流处理基础)
Apache Flink is a framework and distributed processing engine for stateful computations over unbound ...
- Flink系列之Time和WaterMark
当数据进入Flink的时候,数据需要带入相应的时间,根据相应的时间进行处理. 让咱们想象一个场景,有一个队列,分别带着指定的时间,那么处理的时候,需要根据相应的时间进行处理,比如:统计最近五分钟的访问 ...
- Apache Flink系列(1)-概述
一.设计思想及介绍 基本思想:“一切数据都是流,批是流的特例” 1.Micro Batching 模式 在Micro-Batching模式的架构实现上就有一个自然流数据流入系统进行攒批的过程,这在一定 ...
- Flink 1.10 正式发布!——与Blink集成完成,集成Hive,K8S
Apache Flink社区宣布Flink 1.10.0正式发布! 本次Release版本修复1.2K个问题,对Flink作业的整体性能和稳定性做了重大改进,同时增加了对K8S,Python的支持. ...
- kerberos系列之flink认证配置
大数据安全系列的其它文章 https://www.cnblogs.com/bainianminguo/p/12548076.html-----------安装kerberos https://www. ...
- zookeeper 各节点数据保证是弱一致性
一致性保证: ZooKeeeper 是一个高性能的,可扩展的服务.不管是读和写操作是被设计成快速,虽然读比写快. 这样做的原因是在读的情况下,Zookeeper 可以提供旧的数据, 反过来又是由于Zo ...
随机推荐
- alg-最长回文字符串
class Solution { public: std::string longestPalindrome(const std::string& s) { if (s.empty()) { ...
- 浅谈ArrayList
浅谈ArrayList 废话不多说(事实是不会说),让我们直接进入正题 首先讲一讲最基本的ArrayList的初始化,也就是我们常说的构造函数,ArrayList给我们提供了三种构造方式,我们逐个来查 ...
- 【图解】你还在为 TCP 重传、滑动窗口、流量控制、拥塞控制发愁吗?看完图解就不愁了
每日一句英语学习,每天进步一点点: 前言 前一篇「硬不硬你说了算!近 40 张图解被问千百遍的 TCP 三次握手和四次挥手面试题」得到了很多读者的认可,在此特别感谢你们的认可,大家都暖暖的. 来了,今 ...
- Sprint 5 summary: UI 界面更新,Azure端部署和用户反馈分析 12/28/2015
本次sprint主要完成的任务有对手机APP的UI界面的更新,同时对Azure客户端的部署进行了相应的学习和有关的程序设计.同时对于ALPHA release的用户反馈做出相应的分析以确定接下来工作的 ...
- D - A Game with Traps-- codeforces 1260D A
题目大意: 一共有m个士兵,k个陷阱,时间为t,一个首领,这个首领需要在t时间内尽可能多的将士兵带到boos的面前, 第二行是每个士兵的灵敏度. 紧接着是k个陷阱,每个陷阱有l,,r,,d组成,l代表 ...
- 学习JVM参数前必须了解的
JVM参数是什么 大家照相通常使用手机就够用了,但是针对发烧友来说会使用更专业的设备,比如单反相机,在单反里有好几个模式,P/A/S/M,其中P是傻瓜模式,程序会自动根据环境设置快门速度和光圈大小,以 ...
- Unity 游戏框架搭建 2019 (三十二、三十三) 类的命名 & 代码文件命名
昨天我们完成了第八个示例的第二个 MenuItem 菜单顺序的调整. 我们今天再往下接着调整. 我们来看下接下来的 MenuItem 代码如下: [MenuItem("QFramework/ ...
- 最全的 API 接口集合
对于程序员来说,为自己的程序选择一些合适的API并不是那么简单,有时候还会把你搞得够呛,今天猿妹要和大家分享一个开源项目,这个项目汇集了各种开发的api,涵盖了音乐.新闻.书籍.日历等,无论你是从事W ...
- 分析 Nanocore
Nanocore是一个非常强大的木马控制系统,当Nanocore木马运行时,我们发现Windows Defender可以很好地识别威胁.本文的目的是分析Nanocore的传播方式,它是怎样感染Wind ...
- TensorFlow的图像NCHW与NHWC
import tensorflow as tf x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] with tf.Session() as sess: a = t ...