Flink
一、基本特性
1、Flink简介
Flink 是分布式实时和离线计算引擎,用于在无界数据流和有界数据流上进行有状态的计算, 能在常见集群环境中运行,并能以内存速度和任意规模进行计算。
应用场景包括:实时数据计算、实时数据仓库和 ETL、事件驱动型场景,如告警、监控;此外,随着 Flink 对机器学习的支持越来越完善,还可以被用作机器学习和人工智能
2、Flink特性
- 1.批流一体:Flink从另一个视角看待流处理和批处理,将二者统一起来,流处理看待时输入数据流是无界的;批处理被作为一种特殊的流处理,只是它的输入数据流被定义为有界的。
- 2.Exactly-Once:Flink 通过实现两阶段提交和状态保存来实现端到端的精确一致性语义
- 3.状态管理:Flink在做计算的过程中经常需要存储中间状态,来避免数据丢失和状态恢复。
- 4.时间处理:Flink支持事件时间EventTime、注入时间IngestionTime、处理时间ProcessingTime三种时间,同时也支持watermark来处理滞后数据。
- 5.支持窗口:支持时间驱动的 timeWindow、数据驱动的countWindow,同时支持滚动窗口tumbling windows、滑动窗口sliding windows、会话窗口session windows。滚动窗口中的数据不会叠加;
- 6.利用内存性能:任务的状态始终保留在内存中,如果状态大小超过可用内存,则会保存在能高效访问的磁盘数据结构中,非常低的处理延迟,
性能上: ProcessingTime性能最好, IngestTime次之, EventTime最差
延迟上:EventTime延迟最低,IngestTime次之, ProcessingTime延迟最高
确定性: EventTime确定性最高,IngestTime次之, ProcessingTime最低
3、精确一次Exactly-Once
https://developer.51cto.com/article/643945.html
精确一次消费的概念,出现在消息系统中,通常关注点只和消息的消费有关,但是实际情况远不止如此
端到端的精确一次消费包括了,从消息源发出消息,处理程序接到消息并处理消息,到最后保存结果,整个流程,数据的精确一次处理。
在 Flink 中,端到端精准一次处理的位置有三个:
- 1)Source 端:数据从消息系统进入到 Flink 时,需要保证消息精准一次消费,发生故障时需要支持重设数据的读取位置,如Kafka可以通过offset来实现 ,可以自主维护消息的offset,实现精确一次消费
- 2)Flink 内部端:flink利用 Checkpoint 机制,把状态存盘,发生故障的时候可以恢复,保证内部的状态一致性。
- 3)Sink 端:将处理完的数据sink到下一阶段时,需要保证数据能够准确无误发送到下一阶段。Flink 1.4 版本引入了两阶段提交 Sink,
两阶段提交:
- 1)开始事务:创建一个
临时文件夹
,来写把数据写入到这个文件夹里面; - 2)预提交:将内存中缓存的数据
写入临时文件
并关闭; - 3)正式提交:将之前写完的
临时文件放入目标目录
下。这代表着最终的数据会有一些延迟; - 4)丢弃:丢弃临时文件;
若失败发生在预提交成功后,正式提交前。可以根据状态来提交预提交的数据,也可删
Flink+Kafka 如何实现端到端的 exactly-once 语义
- Flink内部 —— 利用 checkpoint 机制,把状态存盘,发生故障的时候可以恢复,保证内部的状态一致性
- source —— kafka consumer 作为 source,可以将偏移量保存下来,如果后续任务出现了故障,恢复的时候可以由连接器重置偏移量,重新消费数据,保证一致性
- sink —— kafka producer 作为 sink,采用两阶段提交 sink,需要实现一个TwoPhaseCommitSinkFunction
4、状态State
状态State是指,流计算过程中计算节点的中间计算结果或元数据属性,比如 在aggregation过程中要在state中记录中间聚合结果。流计算在增量计算,Failover失败重启都需要state的支撑。
Flink状态主要分为两种类型:
-
1)键控状态Keyed State:状态跟特定的key绑定,API中的keyBy字段,每一个key都有一个属于自己的State,key与key之间的State是不可见的,KeyedState 只能使用在KeyStream上的操作和函数。支持的数据结构有5种ValueState、ListState、MapState、AggregatingState、ReducingState。
-
-
2)算子状态Operator State:状态跟一个特定算子的实例绑定,整个算子只对应一个State对象(相同的并行算子都能访问到状态)。只支持ListState
-
可以将Keyed State视为是已经被分片或分区的Operator State,每个key都有且仅有一个状态分区(state-partition)。
状态后端State backend:
主要负责本地的状态管理,以及将检查点(checkpoint)状态写入远程存储
- 1)MemoryStateBackend: 状态信息是存储在 TaskManager 堆内存中的,checkpoint 的时候将状态保存到 JobManager 的堆内存中。
缺点:只能保存数据量小的状态,状态数据有可能会丢失;优点:开发测试很方便 - 2)FsStateBackend:状态信息存储在 TaskManager 堆内存中的,checkpoint 的时候将状态保存到指定的文件中 (HDFS 等文件系统)。
缺点: 状态大小受TaskManager内存限制(默认支持5M) ;优点: 状态访问速度很快、 状态信息不会丢失;用于: 生产,也可存储状态数据量大的情况 - 3)RocksDBStateBackend:状态信息存储在 RocksDB 数据库 (key-value 的数据存储服务), 最终保存在本地文件中 checkpoint 的时候将状态保存到指定的文件中 (HDFS 等文件系统)
缺点: 状态访问速度有所下降 优点: 可以存储超大量的状态信息 状态信息不会丢失 用于: 生产,可以存储超大量的状态信息
5、水位线WaterMark
WaterMark水位线,可以理解为 一个延迟触发机制,,Flink窗口计算时,通过结合EventTime与WaterMark,来处理迟到的事件,一般等于currentMaxEventTime - delay
,是一个具体时间戳,认为事件 eventTime小于WaterMark的所有数据都已经到达,
如果有窗口的停止时间等于Watermark,也就是Watermark大于某个window的end_time,并这个window左闭右开有数据,那么这个窗口被触发执行。
只有一个waterMark时,一个线程触发当前窗口的waterMark时间,为最大的waterMark时间;
当有多个waterMark时,也就是多并行度多个线程时,触发当前窗口的waterMark时间,取线程间最小的线程内最大waterMark时间
每个事件都有一个CurrentWatermark,相当于每个事件的计算触发时间,由ProcessTime 或者EventTime 改变为 当前watermark时间
Watermark计算方式一般为;
currentMaxEventTime = Math.max(currentMaxEventTime, currentElementEventTime);
new Watermark(currentMaxEventTime - maxOutOfOrderness);
正常事件:EventTime大于currentMaxEventTime,窗口的currentMaxEventTime等于EventTime,不受影响;晚到事件,EventTime小于currentMaxEventTime,窗口的currentMaxEventTime等于上一个大的EventTime,
watermark窗口触发条件:
1.watermark 时间 >= window_end_time
2.在 [window_start_time, window_end_time) 区间中有数据存在,注意是左闭右开的区间,而且是以 event time 来计算的
主要是作用于有窗口重叠的,Flink的窗口不同于spark的窗口,每隔5秒统计一次,不管窗口里面有没有数,到窗口结尾就触发统计,flink的窗口需要一个大于窗口时间的事件来触发,增加waterMark,增加容错,窗口统计会晚设置的延迟几秒
6、容错checkpoint
容错:
- MySQL的思想很容易理解,就像棋谱一样,把每一步都记录下来,binlog。后人读棋谱,可以随时切换到任意一张棋谱,然后跟着每一步的操作重现当时的情景。
- HDFS的思想也比较好理解,怕丢数据,就存成N份。只要写进去最少副本数,就自动会把所有旧副本都覆盖了,最大程度的保存好数据。而且他们都属于离线数据库,随时可以存一个快照。
- 但Flink不一样,MySQL和HDFS都是离线存储,Flink是在线的,是一个数据流呀,不能停,Flink通过Checkpoint机制将某个时刻应用状态State进行快照Snapshot保存到磁盘。
Flink 实现容错,主要通过 状态和Checkpoint机制 。State 用来存储计算过程中的中间状态。Checkpoint 将某个时刻应用状态State进行快照Snapshot保存到磁盘
Flink中的Checkpoint,底层使用了Chandy-Lamport
分布式快照算法,可以保证数据的在分布式环境下的一致性。主要核心是barrier栅栏
,相当于标识符,表示对应用中各个OperatorState进行快照时,到哪个位置。
checkpoint流程
-
在checkpoint过程中,JobManager会按照代码配置的规则,定期触发checkpoint,在所有的source的子任务中注入Barrier栅栏;
-
TaskManager在收到所有上游广播的Barrier栅栏(理解为执行Checkpoint的信号) 后,这 个barri栅栏随着数据流向一直流动,当流入到一个算子的时候,算子会等待所有流的 Barrier 都到达之后,才会开始本地的快照,这种机制被称为
Barrier 对齐
。 flink 1.11后可以非Barrier 对齐,触发checkpoint。 -
在对齐的过程中,算子只会继续处理的来自
未出现 Barrier Channel 的数据
,而其余 Channel 的数据会被写入输入队列,直至在队列满后被阻塞。当所有 Barrier 到达后,算子进行本地快照,输出 Barrier 到下游并恢复正常处理。 -
当整个DAG图的子任务的checkpoint都做完之后,会汇报给JobManager,JobManager则认为这个checkpoint已经完成。
- 流的 barrier ,是 Flink 的 Checkpoint 中的一个核心概念。 多个 barrier 被插入到数据流中,然后作为数据流的一部分随着数据流动(有点类似于 Watermark),这些 barrier 不会跨越流中的数据。
- 每个barrier 会把数据流分成两部分:一部分数据进入当前的快照 ,另一部分数据进入下一个快照。
- 每 个barrier 携带着快照的 id,barrier 不会暂停数据的流动, 所以非常轻量级。
- 在流中, 同一时间可以有来源于多个不同快照的多个 barrier, 这个意味着可 以并发的出现不同的快照.
Flink的checkpoint机制,可以与(stream和state)的持久化存储交互的前提: 持久化的source,它需要支持在一定时间内重放事件。这种sources的典型例子是持久化的消息队列(如 Kafka,RabbitMQ等)或文件系统(比如HDFS等
https://developer.aliyun.com/article/926456
https://blog.csdn.net/qq_24095055/article/details/124564178
Flink Checkpoint 基于 Chandy-Lamport 算法
的分布式快照
Chandy-Lamport 算法
,将分布式系统抽象成 DAG,节点表示进程,边表示两个进程间通信的管道。分布式快照的目的,是记录下整个系统的状态,即可分为节点的状态(进程的状态),和边的状态(传输中的数据)。因为系统状态是由输入的消息序列
,驱动变化的,我们可以将输入的消息序列分为多个较短的子序列
,图的每个节点或边先后处理完某个子序列后,都会进入同一个稳定的全局统状态。利用这个特性,系统的进程和信道在子序列的边界点分别进行本地快照,即使各部分的快照时间点不同,最终也可以组合成一个有意义的全局快照。
Flink 通过在 DAG 数据源,定时向数据流注入名为 Barrier 的特殊元素,将连续的数据流切分为多个有限序列
,对应多个 Checkpoint 周期。每当接收到 Barrier,算子进行本地的 Checkpoint 快照,并在完成后异步上传本地快照,同时将 Barrier 以广播方式发送至下游。当某个 Checkpoint 的所有 Barrier 到达 DAG 末端且所有算子完成快照,则标志着全局快照的成功。
比起其他分布式快照,该算法的优势在于辅以 Copy-On-Write 技术的情况下不需要 “Stop The World”影响应用吞吐量,同时基本不用持久化处理中的数据,只用保存进程的状态信息,大大减小了快照的大小。
当作业出现反压时,阻塞式的 Barrier 对齐反而会加剧作业的反压,甚至导致作业的不稳定。
- 首先, Chandy-Lamport 分布式快照的结束依赖于 Marker 的流动,而反压则会限制 Marker 的流动,导致快照的完成时间变长甚至超时。
- 其次,Barrier 对齐本身可能成为一个反压的源头,影响上游算子的效率,而这在某些情况下是不必要的。
flink是基于Chandy-Lamport算法来实现全局快照的,其核心就是在数据中间穿插barrier;当一个task上游同一批次所有的barrier到齐时,就可以触发快照状态的保存了,问题就是出在这里,等待对齐。在flink1.11
版本引入了非对齐的checkpoint,来解决这种阻塞问题,
-
如果当第一个barrier来的时候,我不能触发checkpoint, 是因为还有部分数据没有处理到。
-
直接把这部分还没处理的数据(在buffer里面的数据),连同状态数据一起保存到checkpoint里面;
-
在从checkpoint恢复的时候,就先把这部分buffer数据, 先恢复到当前task的buffer里面,继续计算就可以了,其实弱化了每个checkpoint批次的概念;
-
这样一来当收到第一个barrier的时候,就可以直接触发checkpoint了
对齐与非对齐,两者的差异主要可以总结为两点:
- 快照的触发,是在接收到第一个 Barrier 时还是在接收到最后一个 Barrier 时。
- 是否需要阻塞已经接收到 Barrier 的 Channel 的计算。
7、Flink内存管理
Flink本身基本是以Java语言完成的,理论上说,直接使用JVM的虚拟机的内存管理就应该更简单方便,但Flink还是单独抽象出了自己的内存管理。
https://blog.51cto.com/u_15294985/5134340
因为Flink是为大数据而产生的,而大数据使用会消耗大量的内存,而JVM的内存管理管理设计是兼顾平衡的,不可能单独为了大数据而修改,这对于Flink来说,非常的不灵活,而且频繁GC会导致长时间的机器暂停应用,这对于大数据的应用场景来说也是无法忍受的。
JVM在大数据环境下存在的问题:
- 1)Java 对象存储密度低。在HotSpot JVM中,每个对象占用的内存空间必须是8的倍数,那么一个只包含 boolean 属性的对象就要占用了16个字节内存:对象头占了8个,boolean 属性占了1个,对齐填充占了7个。而实际上我们只想让它占用1个bit。
- 2)在处理大量数据会生成大量对象,尤其是几十甚至上百G的内存应用时
- 3)Java GC可能会被反复触发,其中Full GC或Major GC的开销是非常大的,GC 会达到秒级甚至分钟级。
- 4)OOM 问题影响稳定性。OutOfMemoryError是分布式计算框架经常会遇到的问题,当JVM中所有对象大小超过分配给JVM的内存大小时,就会发生OutOfMemoryError错误,导致JVM崩溃,分布式框架的健壮性和性能都会受到影响。
Flink的内存管理,是在JVM的基础之上封装的内存管理;除了JVM之上封装的内存管理,还会有个一个很大的堆外内存(可以理解为使用操作系统内存),用来执行一些IO操作。
taskmanager进程内存 = Flink内存 +JVM特有内存
Flink内存 = 框架堆内和堆外内存 + Task堆内和堆外内存 + 网络缓冲内存+管理内存
- 1) JVM 特定内存:JVM 本身使用的内存,包含 JVM 的元空间和 over-head
- JVM 的元空间,默认 256mb
- JVM over-head 执行开销:JVM 执行时自身所需要的内容,包括线程堆栈、IO、编译缓存等所使用的内存。.fraction默认 0.1;min,默认 192mb;max,默认 1gb
- 2) 框架内存:Flink 框架,即 TaskManager 本身所占用的内存,不计入 Slot 的资源中
堆内内存
:size默认 128MB堆外内存
:size默认 128MB
- 3) Task 内存:Task 执行用户代码时所使用的内存
堆内内存
:size默认 none,由 Flink 内存扣除掉其他部分的内存得到。堆外内存
:size默认 0,表示不使用堆外内存
- 4) 网络内存