Flink 是分布式实时和离线计算引擎,用于在无界数据流和有界数据流上进行有状态的计算, 能在常见集群环境中运行,并能以内存速度和任意规模进行计算。
应用场景包括:实时数据计算、实时数据仓库和 ETL、事件驱动型场景,如告警、监控;此外,随着 Flink 对机器学习的支持越来越完善,还可以被用作机器学习和人工智能
性能上: ProcessingTime性能最好, IngestTime次之, EventTime最差
延迟上:EventTime延迟最低,IngestTime次之, ProcessingTime延迟最高
确定性: EventTime确定性最高,IngestTime次之, ProcessingTime最低
https://developer.51cto.com/article/643945.html
精确一次消费的概念,出现在消息系统中,通常关注点只和消息的消费有关,但是实际情况远不止如此
端到端的精确一次消费包括了,从消息源发出消息,处理程序接到消息并处理消息,到最后保存结果,整个流程,数据的精确一次处理。
在 Flink 中,端到端精准一次处理的位置有三个:
两阶段提交:
临时文件夹
,来写把数据写入到这个文件夹里面;写入临时文件
并关闭;临时文件放入目标目录
下。这代表着最终的数据会有一些延迟;若失败发生在预提交成功后,正式提交前。可以根据状态来提交预提交的数据,也可删
Flink+Kafka 如何实现端到端的 exactly-once 语义
状态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)状态写入远程存储
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,增加容错,窗口统计会晚设置的延迟几秒
容错:
- 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 对齐反而会加剧作业的反压,甚至导致作业的不稳定。
flink是基于Chandy-Lamport算法来实现全局快照的,其核心就是在数据中间穿插barrier;当一个task上游同一批次所有的barrier到齐时,就可以触发快照状态的保存了,问题就是出在这里,等待对齐。在flink1.11
版本引入了非对齐的checkpoint,来解决这种阻塞问题,
如果当第一个barrier来的时候,我不能触发checkpoint, 是因为还有部分数据没有处理到。
直接把这部分还没处理的数据(在buffer里面的数据),连同状态数据一起保存到checkpoint里面;
在从checkpoint恢复的时候,就先把这部分buffer数据, 先恢复到当前task的buffer里面,继续计算就可以了,其实弱化了每个checkpoint批次的概念;
这样一来当收到第一个barrier的时候,就可以直接触发checkpoint了
对齐与非对齐,两者的差异主要可以总结为两点:
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堆内和堆外内存 + 网络缓冲内存+管理内存
堆内内存
:size默认 128MB堆外内存
:size默认 128MB堆内内存
:size默认 none,由 Flink 内存扣除掉其他部分的内存得到。堆外内存
:size默认 0,表示不使用堆外内存堆外内存
: fraction,默认 0.1 ; min,默认 64mb; .max,默认 1gb堆外内存
: fraction,默认 0.4; size,默认 none如果 size 没指定,则等于 Flink 内存*fractionFlink相对于Spark,堆外内存该用还是用, 堆内内存管理做了自己的封装,不受JVM的GC影响 。
基于Yarn模式,一般参数指定的是总进程内存,taskmanager.memory.process.size,
比如指定为 4G,每一块内存得到大小如下:
(1)计算 Flink 内存
JVM 元空间 256m
JVM 执行开销: 4g*0.1=409.6m,在[192m,1g]之间,最终结果 409.6m
Flink 内存=4g-256m-409.6m=3430.4m
(2)网络内存=3430.4m0.1=343.04m,在[64m,1g]之间,最终结果 343.04m
(3)托管内存=3430.4m0.4=1372.16m
(4)框架内存,堆内和堆外都是 128m
(5)Task 堆内内存=3430.4m-128m-128m-343.04m-1372.16m=1459.2m
Flink除了对堆内内存做了封装之外,还实现了自己的序列化和反序列化机制,
序列化与反序列化可以理解为编码与解码的过程。序列化以后的数据希望占用比较小的空间,而且数据能够被正确地反序列化出来。为了能正确反序列化,序列化时仅存储二进制数据本身肯定不够,需要增加一些辅助的描述信息。此处可以采用不同的策略,因而产生了很多不同的序列化方法。
目前,绝大多数的大数据计算框架,都是基于JVM实现的,为了快速地计算数据,需要将数据加载到内存中进行处理。
当大量数据需要加载到内存中时,如果使用Java序列化方式来存储对象,占用的空间会较大降低存储传输效率。Java序列化方式存储对象存储密度是很低的。Java生态系统中有挺多的序列化框架,例如:Kryo、Avro、ProtoBuf等。
Flink 摒弃了 Java 原生的序列化方法, 实现了自己的序列化框架,使用TypeInformation
表示每种数据类型,所以可以只保存一份对象Schema信息,节省存储空间。又因为对象类型固定,所以可以通过偏移量存取。
PojoTypeInfo
,而PojoTypeInfo是TypeInformation
的子类。Flink是使用Java的序列化方式吗?
Java序列化方式有什么问题?
Java中是用Class描述类型,Flink也是用Class描述吗?
请解释一下Java类型擦除。
Flink中为什么使用Lambda表达式实现flatMap需要通过returns指定类型呢?
new ArrayList()和new ArrayList(){}的区别是什么?
(1) local方式(本地测试用)
该方式是在Java虚拟机上运行Flink程序,或者是在正在运行程序的Java虚拟机上,像我们在IDE上直接运行就是采用的local方式,这种方式会获取到一个
LocalExecutionEnvironment
(或者CollectionEnvironment)类的环境上下文对象,默认并行度是当前可用处理器的Java虚拟机的数量
(1)Standalone 模式
配置一个或多个JobManager(HA模式),和一台或多台TaskManager,通过flink中bin下面的start-cluster.sh启动,关于这种方式的启动
(2)Yarn 模式
以 Yarn 模式部署 Flink 任务时,要求 Flink 是有 Hadoop 支持的版本,Hadoop 环境需要保证版本在 2.2以上,并且集群中安装有 HDFS 服务。
1)Session-Cluster 模式
在 yarn 中初始化一个 flink 集群,开辟指定的资源,以后提交任务都向这里提交。这个 flink 集群会常驻在 yarn 集群中,除非手工停止。
需要先启动集群,然后再提交作业,接着会向 yarn 申请一块空间后,资源永远保持不变。如果资源满了,下一个作业就无法提交,只能等到yarn中的其中一个作业执行完成后,释放了资源,下个作业才会正常提交。
- 所有作业共享 Dispatcher 和 ResourceManager;共享资源;适合规模小执行时间短的作业。
任务启动步骤:
- 启动 hadoop 集群(略)
- 启动 yarn-session
./yarn-session.sh -n 2 -s 2 -jm 1024 -tm 1024 -nm test -d
其中:
-n(–container):TaskManager 的数量。
-s(–slots): 每个 TaskManager 的 slot 数量,默认一个 slot 一个 core,默认每个taskmanager 的 slot 的个数为 1,有时可以多一些 taskmanager,做冗余。
-jm:JobManager 的内存(单位 MB)。
-tm:每个 taskmanager 的内存(单位 MB)。
-nm:yarn 的 appName(现在 yarn 的 ui 上的名字)。
-d:后台执行。
- 执行任务
./flink run -c com.atguigu.wc.StreamWordCount FlinkTutorial-1.0.jar --host lcoalhost –port 7777
;- 去 yarn 控制台查看任务状态
- 取消 yarn-session
yarn application --kill application_1577588252906_0001
2) Per-Job-Cluster 模式:
每次提交都会创建一个新的 flink 集群,任务之间互相独立,互不影响,方便管理。任务执行完成之后创建的集群也会消失。
一个 Job 会对应一个集群,每提交一个作业会根据自身的情况,都会单独向 yarn申请资源,直到作业执行完成,一个作业的失败与否并不会影响下一个作业的正常 提交和运行。
- 独享 Dispatcher 和 ResourceManager,按需接受资源申请;适合规模大 长时间运行的作业。
任务启动步骤:
- 启动 hadoop 集群(略)
- 不启动 yarn-session,直接执行 job
./flink run –m yarn-cluster -c com.atguigu.wc.StreamWordCount FlinkTutorial-1.0.jar --host lcoalhost –port 7777
模式 | 提交语句 | 要求 |
---|---|---|
StandAlnoe模式 | flink run -c streaming.slot.lesson01.WordCount -p 2 flinklesson-1.0-SNAPSHOT.jar | |
Flink on Yarn Per-Job-Cluster | flink run -m yarn-cluster -p 2 -yn 2 -yjm 1024 -ytm 1024 -c streaming.slot.lesson01.WordCount flinklesson-1.0-SNAPSHOT.jar 特点: 一个任务一个集群 | 掌握 |
Flink on Yarn yarn-session | yarn-session.sh -n 2 -jm 1024 -tm 1024 flink run ./examples/batch/WordCount.jar -input hdfs://hadoop100:9000/LICENSE -output hdfs://hadoop100:9000/wordcount-result.txt |
各种模式总结
Application Mode 与 Per-Job Mode 类似,它主要是为了解决 Per-Job Mode 中由于 client 端导致的 带宽、CPU 问题 Session Mode
向 Flink 集群提交任务,共有以下两种方式:
一、flink run参数
flink run -c streaming.slot.lesson01.WordCount -p 2 flinklesson-1.0-SNAPSHOT.jar
二、flink run -m yarn-cluster参数
flink run -m yarn-cluster -p 2 -yn 2 -yjm 1024 -ytm 1024 -c streaming.slot.lesson01.WordCount flinklesson-1.0-SNAPSHOT.jar
1.DataStream API
:对数据流进行流处理操作,将流式的数据抽象成分布式的数据流,支持 Java 和 Scala;2.DataSet API
:对静态数据进行批处理操作,将静态数据抽象成分布式的数据集,支持 Java、Scala 和 Python;3.Table API
:对结构化数据进行查询操作,将结构化数据抽象成关系表,并通过类 SQL 的 DSL 对关系表进行各种查询操作,支持 Java 和 Scala。4.Flink ML
:提供了机器学习 Pipelines API 并实现了多种机器学习算法;Gelly、Flink 的图计算库提供了图计算的相关 API 及多种图计算算法的实现。Flink 程序的基本构建是数据输入来自一个 Source,Source 代表数据的输入端,经过 Transformation 进行转换,然后在一个或者多个 Sink 接收器中结束。
数据流(Stream)就是一组永远不会停止的数据记录流,而转换(Transformation)是将一个或多个流作为输入,并生成一个或多个输出流的操作。在执行时,Flink 程序映射到 Streaming Dataflows,由流(Streams)和转换操作(Transformation Operators)组成。
CEP全称为Complex Event Processing
,复杂事件处理
Flink CEP是在 Flink 中实现的复杂事件处理(CEP)库,CEP 允许在无休止的事件流中检测事件模式,让我们有机会掌握数据中重要的部分。一个或多个由简单事件构成的事件流通过一定的规则匹配,然后输出用户想得到的数据 —— 满足规则的复杂事件
公司自建机房:三个机架15台物理机:
128G内存,20核40线程物理CPU,10T硬盘,单台报价4W出头。
大数据服务器,7台,数据仓库,spark集群计算,flink集群计算,数据主要为hbase存储数据与保存到hdfs的日志数据,
CDH:国内使用最多的版本,但 CM不开源,但其实对中、小公司使用来说没有影响(建议使用)
其他服务,5台,部署其他服务,数据库、ngix、消息系统mqtt集群,前端组态,后端Java程序运行代码等。
测试服务器,3台
Flink 提供了专门的 Kafka 连接器,向 Kafka topic 中读取或者写入数据。
原文链接:
Flink中的Kafka消费者,集成了 Flink 的 Checkpoint 机制。触发checkpoint时, 每个分区的offset都存储在checkpoint中。 可以 实现exactly-once 的处理语义
消费模式是auto.offset.reset=eraliest
Flink消费一些数据并且提交了checkpoint, 如果发生故障,Flink将通过从checkpoint,加载状态后端,从上一次的checkpoint提交的offset处开始消费。,继续恢复应用程序,可以做到所谓的断点续传。
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(5000);
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
env.setParallelism(1);
Properties props = new Properties();
props.setProperty("bootstrap.servers",KAFKA_BROKER);
props.setProperty("zookeeper.connect", ZK_HOST);
props.setProperty("group.id",GROUP_ID);
props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
FlinkKafkaConsumer011<String> consumer = new FlinkKafkaConsumer011<>(TOPIC, new SimpleStringSchema(), props);
//方式一 : 默认:从topic中指定的group上次消费的位置开始消费,必须配置group.id参数
consumer.setStartFromGroupOffsets();
//方式二: 从topic中最初的数据开始消费
consumer.setStartFromEarliest();
//方式三: 从指定的时间戳开始
consumer.setStartFromTimestamp(1559801580000l);
//方式四: 从最新的数据开始消费
consumer.setStartFromLatest();
//方式五:Flink从topic中指定的offset开始,这个比较复杂,需要手动指定offset
Map<KafkaTopicPartition, Long> offsets = new HashedMap();
offsets.put(new KafkaTopicPartition("test", 0), 11111111l);
offsets.put(new KafkaTopicPartition("test", 1), 22222222l);
offsets.put(new KafkaTopicPartition("test", 2), 33333333l);
consumer.setStartFromSpecificOffsets(offsets);
/*
Map Long参数,指定的offset位置
KafkaTopicPartition构造函数有两个参数,第一个为topic名字,第二个为分区数
获取offset信息,可以用过Kafka自带的kafka-consumer-groups.sh脚本获取
*/
一般使用,默认行为(setStartFromGroupOffsets
):从topic中指定的group上次消费的位置开始消费。
必须配置group.id参数,从消费者组提交的偏移量开始读取分区 。
auto.offset.reset
将使用属性中的设置。auto.offset.reset
设置的属性进行消费。但是如果程序带有状态的算子,还是建议使用检查点重启。Spark和Flink都具有流和批处理能力,但是他们的做法是相反的。
同时,Flink相比于Spark而言还有诸多明显优势:
1)flink更轻量,可以根据时间戳更新state,
轻量级的分布式快照,实现了每个操作符的快照,及循环流的在循环的数据的快照
对比 Spark : 重量的快照,Spark每次全量的快照,
Flink 每次增量的快照,spark是每个批次全量保存
2)在Flink中的Checkpoint中,有精确一次概念和用法,而spark checkpoint没有仅一次的概念
3)flink的 checkpoint有三个状态后端:memery、rocksdb、hdfs;在Spark中checkpoint的存储位置一般保存在HDFS 与本地磁盘
keyBy算子将DataStream转换成一个KeyedStream。KeyedStream是一种特殊的DataStream, 事实上,KeyedStream继承了DataStream,DataStream的各元素随机分布在各Task Slot中, KeyedStream的各元素按照Key分组,分配到各Task Slot中。
groupBy按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。
Flink可以完全独立于Hadoop,在不依赖Hadoop组件下运行。但是做为大数据的基础设施,Hadoop体系是任何大数据框架都绕不过去的。Flink可以集成众多Hadooop 组件,例如Yarn、Hbase、HDFS等等。例如,Flink可以和Yarn集成做资源调度,也可以读写HDFS,或者利用HDFS做检查点。
有时候需要求uv ,内存或者状态中存过多数据,导致压力巨大, 这个时候可以结合Redis 或者 布隆过滤器来去重。
注意:布隆过滤器存在非常小的误判几率,不能判断某个元素一定百分之百存在,所以只能用在允许有少量误判的场景,不能用在需要100%精确判断存在的场景。
一般有离线Job来恢复和完善实时数据。
参考
Flink 运行时由两种类型的进程组成:一个 JobManager 和一个或者多个 TaskManager。
1.Client:Flink 程序提交的客户端,用于准备数据流并将其发送给 JobManager。
2.JobManager:管理节点, 主要负责 任务调度、资源管理。包括,接收 Flink 作业job、调度 Task、收集作业状态、管理 TaskManager、协调 Task 做 checkpoint、Failover 故障恢复等
3.TaskManager:计算节点,主要负责,具体任务执行、对应任务在每个节点上的资源申请和管理。 在启动的时候将资源的状态向 JobManager 汇报。
Flink集群启动后 , 会启动一个JobManger和一个或多个的TaskManager进程。TaskManager 在启动的时候就设置好了槽位数(Slot),每个 slot 能启动一个Task,Task 为线程。
JobManager 相当于整个集群的 Master 节点,且整个集群有且只有一个活跃的 JobManager ,负责整个集群的任务管理和资源管理。
从客户端中获取提交的应用,然后根据集群中 TaskManager 上 TaskSlot 的使用情况,为提交的应用分配相应的 TaskSlot 资源并命令 TaskManager 启动从客户端中获取的应用。
JobManager 和 TaskManager 之间通过 Actor System 进行通信,获取任务执行的情况并通过 Actor System 将应用的任务执行情况发送给客户端。
在任务执行的过程中,Flink JobManager 会触发 Checkpoint 操作,每个 TaskManager 节点 收到 Checkpoint 触发指令后,完成 Checkpoint 操作,所有的 Checkpoint 协调过程都是在 Fink JobManager 中完成。
当任务完成后,Flink 会将任务执行的信息反馈给客户端,并且释放掉 TaskManager 中的资源以供下一次提交任务使用。
JobManager 这个进程由三个不同的组件组成:
ResourceManager
资源管理器
负责 Flink 集群中的资源提供、回收、分配 、管理 task slots。Flink 为不同的环境和资源提供者(例如 YARN、Kubernetes 和 standalone 部署)实现了对应的 ResourceManager。在 standalone 设置中,ResourceManager 只能分配可用 TaskManager 的 slots,而不能自行启动新的 TaskManager。
Dispatcher
分发器
提供了一个 REST 接口,用来提交 Flink 应用程序执行,并为每个提交的作业启动一个新的 JobMaster。它还运行 Flink WebUI 用来提供作业执行信息。
JobMaster
作业管理器
JobMaster 负责管理单个JobGraph的执行。Flink 集群中可以同时运行多个作业,每个作业都有自己的 JobMaster。
TaskManager 相当于整个集群的 Slave 节点,负责具体的任务执行和对应任务在每个节点上的资源申请和管理。
Flink 的任务运行其实是采用多线程的方式,这和 MapReduce 多 JVM 进行的方式有很大的区别,Flink 能够极大提高 CPU 使用效率,在多个任务和 Task 之间通过 TaskSlot 方式共享系统资源,每个 TaskManager 中通过管理多个 TaskSlot 资源池进行对资源进行有效管理。
Flink job:作业,类似Storm中的Topology,同样对应着Flink中的一层层的图,从StreamGraph --> JobGraph --> ExecutionGraph --> 物理执行图。
Task:类似于Storm中的Bolt,为了拓扑更高效地运行,Flink提出了Chaining,尽可能地将operators chain在一起作为一个task来处理。对应于JobGraph中的JobVertex。
operator:算子,对应于StreamGraph中的StreamNode。
在 Flink 中,一个 TaskManger 就是一个 JVM 进程,会用独立的线程来执行 Task。
任务调度流程:
Client
,该 Client
首先会对用户提交的 Flink 程序代码进行预处理,获取 JobManager
的地址,并建立到 JobManager
的连接,将 Flink Job 提交给 JobManager
。JobManager
从 Client
处接收到 Job后,会生成优化后的执行计划,并以 Task 的单元调度到各个TaskManager 去执行。TaskManager
从 JobManager
处接收需要部署的 Task,部署启动后,与自己的上游建立 Netty 连接,接收数据并处理,并 将心跳和统计信息汇报给 JobManager
。TaskManager
之间以流的形式进行数据的传输。上述三者均为独立的 JVM 进程。
Client
,该 Client
首先会对用户提交的 Flink 程序代码进行预处理,将 Stream API 编写的代码生成的最初的StreamGraph图,并经过优化后生成了JobGraph图;JobManager
的地址,并建立到 JobManager
的连接,将 Flink JobGraph提交给 JobManager
。JobManager
从 Client
处接收到 Job后,根据 JobGraph 生成ExecutionGraph图,会生成优化后的物理执行图,并以 Task 的单元调度到各个TaskManager 去执行。TaskManager
从 JobManager
处接收需要部署的 Task,部署启动后,与自己的上游建立 Netty 连接,接收数据并处理,并 将心跳和统计信息汇报给 JobManager
。TaskManager
之间以流的形式进行数据的传输。注意:
Client 为提交 Job 的客户端,可以是运行在任何机器上(与 JobManager 环境连通即可)。提交 Job 后,Client 可以结束进程(Streaming 的任务),也可以不结束并等待结果返回。
可以通过多种方式启动 JobManager 和 TaskManager:直接在机器上作为standalone 集群启动、在容器中启动、或者通过YARN等资源框架管理并启动。TaskManager 连接到 JobManagers,宣布自己可
必须始终至少有一个 TaskManager。在 TaskManager 中资源调度的最小单位是 task slot。TaskManager 中 task slot 的数量表示并发处理 task 的数量。请注意一个 task slot 中可以执行多个算子
Client
向Yarn ResourceManager
提交任务,同时向HDFS上传Flink的Jar包和配置,以便后续启动 Flink 相关组件的容器。Yarn ResourceManager
分配Container资源,并通知对应的Yarn NodeManager
启动ApplicationMaster
;ApplicationMaster
启动后加载Flink的Jar包和配置构建环境,然后启动JobManager
,之后ApplicationMaster
向Yarn ResourceManager
申请资源, 分配资源后,再通知资源所在节点的Yarn NodeManager
启动TaskManager
;NodeManager
加载Flink的Jar包和配置构建环境,并启动TaskManager
;TaskManager启动后向JobManager发送心跳包,并等待JobManager向其分配任务。会话(Session)模式
在会话模式下,我们需要先启动一个YARN session,这个会话会创建一个 Flink 集群。 这里只启动了 JobManager,而 TaskManager 可以根据需要动态地启动。在 JobManager 内 部,由于还没有提交作业,所以只有 ResourceManager 和 Dispatcher 在运行,
(1)客户端通过 REST 接口,将作业提交给分发器。
(2)分发器启动 JobMaster,并将作业(包含 JobGraph)提交给 JobMaster。
(3)JobMaster 向资源管理器请求资源(slots)。
(4)资源管理器向 YARN 的资源管理器请求 container 资源。
(5)YARN 启动新的 TaskManager 容器。
(6)TaskManager 启动之后,向 Flink 的资源管理器注册自己的可用任务槽。
(7)资源管理器通知 TaskManager 为新的作业提供 slots。
(8)TaskManager 连接到对应的 JobMaster,提供 slots。
(9)JobMaster 将需要执行的任务分发给 TaskManager,执行任务。
单作业(Per-Job)模式
在单作业模式下,Flink 集群不会预先启动,而是在提交作业时,才启动新的 JobManager。
(1)客户端将作业提交给 YARN 的资源管理器,这一步中会同时将 Flink 的 Jar 包和配置上传到 HDFS,以便后续启动 Flink 相关组件的容器。
(2)YARN 的资源管理器分配 Container 资源,启动 Flink JobManager,并将作业提交给JobMaster。这里省略了 Dispatcher 组件。
(3)JobMaster 向资源管理器请求资源(slots)。
(4)资源管理器向 YARN 的资源管理器请求 container 资源。
(5)YARN 启动新的 TaskManager 容器。
(6)TaskManager 启动之后,向 Flink 的资源管理器注册自己的可用任务槽。
(7)资源管理器通知 TaskManager 为新的作业提供 slots。
(8)TaskManager 连接到对应的 JobMaster,提供 slots。
(9)JobMaster 将需要执行的任务分发给 TaskManager,执行任务。
可见,区别只在于 JobManager 的启动方式,以及省去了分发器。当第 2 步作业提交给JobMaster,之后的流程就与会话模式完全一样了。
TaskManager 相当于整个集群的 Slave 节点,负责具体的任务执行和对应任务在每个节点上的资源申请和管理。
相比 JobManager而言,TaskManager 的启动流程较为简单,启动类入口为org.apache.flink.runtime.taskexecutor.TaskManagerRunner
。
启动过程中主要进行 Slot 资源的分配、RPC 服务的初始化,以及JobManager 进行通信等。
Flink 的任务运行,其实是采用多线程的方式,这和 MapReduce 多 JVM 并行的方式有很大的区别,
Flink 中每一个 worker(TaskManager)都是一个 JVM 进程,它可能会在独立的线程上执行一个或多个 subtask。为了控制一个 worker 能接收多少个 task,worker 通过 task slot 来进行控制(一个 worker 至少有一个 task slot。
在多个任务和 Task 之间,通过TaskSlot方式共享系统资源,每个 TaskManager 通过管理多个 TaskSlot 资源池,对资源进行有效管理。
(1)TaskSlot
Flink 中的计算资源通过 Task Slot 来定义。每个 task slot 代表了 TaskManager 的一个固定大小的资源子集。例如,一个拥有3个slot的 TaskManager,会将其管理的内存平均分成三分分给各个 slot。
优点:
(2)共享Slot:SlotSharingGroup
所谓的共享Slot,就是指:不同operator下面的subTask,可以在同一个Task Slot中运行,即共享Slot。(这里是说,一个operator往往会因为并行度的原因,被分解成并行度个数的Task,并行执行)
在Storm中,supervisor下面是work,work中往往一个Executor执行一个Task。
而在Flink中,TaskManager下面是slot,相同的是Slot和Work都是一个JVM进程,不同的是TaskManager会对Slot进行资源分配。
优点:
并行度,某一个算子被切分成多少个子任务。
Flink 本并行度的优先级依次是**:1算子级别 > 2环境级别 > 3客户端级别 > 4集群配置级别。**
Task Slot 是静态的概念,是指 TaskManager 具有的并发执行能力,可以通过参数taskmanager.numberOfTaskSlots
进行配置;
而并行度 parallelism 是动态概念,即 TaskManager 运行程序时实际使用的并发能力,可以通过参数 parallelism.default
进行配置。
也就是说,假设一共有 3 个 TaskManager,每一个 TaskManager 中的分配 3 个TaskSlot,也就是每个 TaskManager 可以接收 3 个 task,一共 9 个 TaskSlot
,如果我们设置 parallelism.default=1,即运行程序默认的并行度为 1,9 个 TaskSlot 只用了 1个,有 8 个空闲
,因此,设置合适的并行度才能提高效率。
在Flink中的执行图可以分成四层:StreamGraph -> JobGraph -> ExecutionGraph -> 物理执行图。
StreamGraph
:是根据用户通过 Stream API 编写的代码生成的最初的图,表示程序的拓扑结构,在client中生成。JobGraph
:StreamGraph经过优化后生成了 JobGraph,提交给 JobManager 的数据结构。在client中生成。
ExecutionGraph
:JobManager 根据 JobGraph 生成ExecutionGraph。 在JobManager 中生成。
物理执行图
:JobManager 根据 ExecutionGraph 对 Job 进行调度后,在各个TaskManager 上部署 Task 后形成的“图”,并不是一个具体的数据结构。概述
Operator chains,存在于Flink中StreamGraph --> JobGraph
的过程中,将多个符合条件的StreamNode节点 chain在一起,作为一个JobNode节点的优化行为。(StreamNode就是Operator,因此称为Operator chains)
为了更高效地分布式执行,Flink会尽可能地将operator的subtask链接(chain)在一起形成task。每个task在一个线程中执行。将 Operators 链接成 Task 是非常有效的优化,它能减少:
①线程之间的切换;
②消息的序列化/反序列化;
③数据在缓冲区的交换;
④延迟的同时提高整体的吞吐量。
可以进行Operator chains的条件
source是程序的数据源输入,你可以通过StreamExecutionEnvironment.addSource(sourceFunction)
来为你的程序添加一个source。
flink提供了大量的已经实现好的source方法,你也可以自定义source:
(1)通过实现sourceFunction
接口来自定义无并行度的source
(2)通过实现ParallelSourceFunction
接口 or 继承RichParallelSourceFunction
来自定义有并行度的source
不过大多数情况下,我们使用自带的source即可。
获取source的方式(自带的)
readTextFile(path)
:读取文本文件,文件遵循TextInputFormat 读取规则,逐行读取并返回。socketTextStream
:从socker中读取数据,元素可以通过一个分隔符切开。fromCollection(Collection)
,通过java 的collection集合创建一个数据流,集合中的所有元素必须是相同类型的。addSource
可以实现读取第三方数据源的数据自带的connectors
union 多流合并,类型一致
connect 两条流分别处理,类型可不一致,可共享状态
Union 与connect 区别:
1. Union 之前两个流的类型必须是一样,Connect 可以不一样,在之后的 coMap中再去调整成为一样的。
2. Connect 只能操作两个流,Union 可以操作多个。
join 相当于innerjoin
coGroup 实现左外连接,第一个流没有join上,也要输出
1)流分裂Split与选择Select
Split
:根据某些特征把一个 DataStream 拆分成两个或者多个 DataStream。Select
:从一个 SplitStream 中获取一个或者多个DataStream。Connect
:连接两个保持他们类型的数据流,两个数据流被 Connect 之后,只是被放在了一个同一个流中,内部依然保持各自的数据和形式不发生任何变化,两个流相互独立。CoMap/CoFlatMap
:作用于 ConnectedStreams 上,功能与 mapUnion
:对两个或者两个以上的 DataStream 进行 union 操作,产生一个包含所有 DataStream 元素的新 DataStream。4)keyby
Reduce
:对数据进行聚合操作,结合当前元素和上一次reduce返回值,进行聚合操作,然后返回一个新的值。Flink 没有类似于 spark 中 foreach 方法,让用户进行迭代的操作。虽有对外的输出操作都要利用 Sink 完成。最后通过类似如下方式完成整个任务最终输出操作。
stream.addSink(new MySink(xxxx))
官方提供了一部分的框架的 sink。除此以外,需要用户自定义实现 sink。
1)滚动窗口
适用场景:适合做 BI 统计等(做每个时间段的聚合计算)。
2) 滑动窗口
适用场景:对最近一个时间段内的统计
第一参数:窗口大小; 第二个参数:滑动步长
每隔5秒,统计最近15秒的
3)window function
window function 定义了要对窗口中收集的数据做的计算操作,可以分为两类:
增量聚合函数:
全窗口函数:
其它可选API
.trigger() ——触发器,定义window什么时候关闭,触发计算并输出结果
.evitor()——移除器,定义移除某些数据的逻辑
.allowedLateness() —一允许处理迟到的数据
.sideOutputLateData() ——将迟到的数据放入侧输出流·
.getSideOutput() ——获取侧输出流
EventTime 的引入
在 Flink 的流式处理中,绝大部分的业务都会使用 eventTime,一般只在
eventTime 无法使用时,才会被迫使用 ProcessingTime 或IngestionTime。
如果要使用 EventTime,那么需要引入 EventTime 的时间属性,引入方式如下所示
系统会周期性的将 watermark 插入到流中(水位线也是一种特殊的事件!)。默认周期是 200 毫秒。可以使用
getCurrentWatermark()方法。如果方法返回一个时间戳大于之前水位的时间戳,新的 watermark
会被插入到流中。这个检查保证了水位线是单调递增的。如果方法返回的时间戳小于等于之前水位的时间戳,则不会产生新的 watermark。
AscendingTimestampExtractor:知道数据流的时间戳是单调递增的
可以使用 AscendingTimestampExtractor,这个类会直接使用数据的时间戳生成 watermark
这种方式不是固定时间的,而是可以根据需要对每条数据进行筛选和处理。
之前学习的转换算子是无法访问事件的时间戳信息和水位线信息的。而这
在一些应用场景下,极为重要。例如 MapFunction 这样的 map 转换算子就无法访问时间戳或者当前事件的事件时间。
DataStream API 提供了一系列的 Low-Level 转换算子。可以访问时间
戳、watermark 以及注册定时事件。还可以输出特定的一些事件,例如超时事件等。
重点KeyedProcessFunction
所有的 Process Function 都继承自RichFunction
接口,所以都有 open()
、close()
和 getRuntimeContext()
等方法。而KeyedProcessFunction还额外提供了两个方法。
processElement(I value, Context ctx, Collector out),
结果将会放在 Collector 数据类型中输出。Context 可以访问元素的时间戳,元素的 key,以及 TimerService 时间服务。Context 还可以将结果输出到别的流(side outputs)。onTimer(long timestamp, OnTimerContext ctx, Collector out)
,是一个回调函数。当之前注册的定时器触发时调用。当定时器 timer 触发时,会执行回调函数 onTimer()。注意定时器 timer 只能在keyed streams 上面使用。
大部分的 DataStream API 的算子的输出是单一输出,也就是某种数据类型的流。除了 split 算子,可以将一条流分成多条流,这些流的数据类型也都相同。
process function 的 side outputs 功能可以产生多条流,并且这些流的数据类型可以不一样。一个 side output 可以定义为 OutputTag[X]对象,X 是输出流的数据类型。process function 可以通过 Context 对象发射一个事件到一个或者多个 side outputs。
需求:将温度值低于 30 度的数据输出到 side output。
3)状态保存
检测传感器的温度值,如果连续的两个温度差值超过 10 度,就输出报警
基于文件,readTextFile(path)
基于集合,fromCollection(Collection)
产生数据倾斜的原因主要有 2 个方面:
因此解决问题的思路也很清晰:
请谈谈你们是如何处理脏数据的?
这也是一个开放性的面试题,建议你结合自己的实际业务来谈。比如可以通过一个 fliter 算子将不符合规则的数据过滤出去。当然了,我们也可以在数据源头就将一些不合理的数据抛弃,不允许进入 Flink 系统参与计算。
这道题要求面试者掌握Flink 框架引擎划分执行计划的详细过程。
一个 Flink 任务的 DAG 生成计算图,大致经历以下3个过程。
是水泵,JobEdge
是水管,而 IntermediateDataSet则是中间的蓄水池Table SQL 是 Flink 提供的高级 API 操作。Flink SQL 是 Flink 实时计算为简化计算模型,降低用户使用实时计算门槛而设计的一套符合标准 SQL 语义的开发语言。
Flink 把 SQL 的 解析、优化 和 执行**教给了Calcite。从图中可以看到,无论是批查询 SQL 还是流式查询 SQL,都会经过对应的转换器 Parser 转换成为节点树 SQLNode tree,然后生成逻辑执行计划 Logical Plan,逻辑执行计划在经过优化后生成真正可以执行的物理执行计划,交给 DataSet 或者 DataStream 的 API 去执行。
https://mp.weixin.qq.com/s/xRqrojjFITuhswtjNJo7OQ
问题:公司怎么提交的实时任务,有多少 Job Manager、Task Manager?
解答:
我们使用 yarn session 模式提交任务;另一种方式是每次提交都会创建一个新的 Flink 集群,为每一个 job 提供资源,任务之间互相独立,互不影响,方便管理。任务执行完成之后创建的集群也会消失。
线上命令脚本如下:bin/yarn-session.sh -n 7 -s 8 -jm 3072 -tm 32768 -qu root.*.* -nm *-* -d
其中申请 7 个 taskManager,每个 8 核,每个 taskmanager 有 32768M 内存。
集群默认只有一个 Job Manager。但为了防止单点故障,我们配置了高可用。
对于 standlone 模式,我们公司一般配置一个主 Job Manager,两个备用 Job Manager,然后结合 ZooKeeper 的使用,来达到高可用;对于 yarn 模式,yarn 在Job Mananger 故障会自动进行重启,所以只需要一个,我们配置的最大重启次数是10 次
问题:怎么做压力测试和监控?
解答:我们一般碰到的压力来自以下几个方面:
一,产生数据流的速度如果过快,而下游的算子消费不过来的话,会产生背压。
背压的监控可以使用 Flink Web UI(localhost:8081) 来可视化监控 Metrics,一旦报警就能知道。一般情况下背压问题的产生可能是由于 sink 这个 操作符没有优化好,做一下优化就可以了。比如如果是写入 ElasticSearch, 那么可以改成批量写入,可以调大 ElasticSearch 队列的大小等等策略。
二,设置 watermark 的最大延迟时间这个参数,如果设置的过大,可能会造成内存的压力。
可以设置最大延迟时间小一些,然后把迟到元素发送到侧输出流中去。
晚一点更新结果。或者使用类似于 RocksDB 这样的状态后端, RocksDB 会开辟堆外存储空间,但 IO 速度会变慢,需要权衡。
三,还有就是滑动窗口的长度如果过长,而滑动距离很短的话,Flink 的性能
会下降的很厉害。
我们主要通过时间分片的方法,将每个元素只存入一个“重叠窗
口”,这样就可以减少窗口处理中状态的写入。参
见链接:
https://www.infoq.cn/article/sIhs_qY6HCpMQNblTI9M
四,状态后端使用 RocksDB,还没有碰到被撑爆的问题
问题:为什么使用 Flink 替代 Spark?
解答:主要考虑的是 flink 的低延迟、高吞吐量和对流式数据应用场景更好的支持;另外,flink 可以很好地处理乱序数据,而且可以保证 exactly-once 的状态一致性。详见文档第一章,有 Flink 和 Spark 的详细对比。
问题:Flink 的 checkpoint 存在哪里?
解答:可以是内存,文件系统,或者 RocksDB。
问题:如果下级存储不支持事务,Flink 怎么保证 exactly-once?
解答:端到端的 exactly-once 对 sink 要求比较高,具体实现主要有幂等写入和事务性写入两种方式。
幂等写入的场景依赖于业务逻辑,更常见的是用事务性写入。而事务性写入又有预写日志(WAL)和两阶段提交(2PC)两种方式。
如果外部系统不支持事务,那么可以用**预写日志的方式,**把结果数据先当成状态保存,然后在收到 checkpoint 完成的通知时,一次性写入 sink 系统。
参见文档 9.2、9.3 节及课件《Flink 的状态一致性》
问题:说一下 Flink 状态机制?
解答:Flink 内置的很多算子,包括源 source,数据存储 sink 都是有状态的。在Flink 中,状态始终与特定算子相关联。Flink 会以 checkpoint 的形式对各个任务的状态进行快照,用于保证故障恢复时的状态一致性。Flink 通过状态后端来管理状态和 checkpoint 的存储,状态后端可以有不同的配置选择。详见文档第九章。
问题:怎么去重?考虑一个实时场景:双十一场景,滑动窗口长度为 1 小时,滑动距离为 10 秒钟,亿级用户,怎样计算 UV?
解答:使用类似于 scala 的 set 数据结构或者 redis 的 set 显然是不行的,
因为可能有上亿个 Key,内存放不下。所以可以考虑使用布隆过滤器(Bloom Filter)来去重。
问题:Flink 的 checkpoint 机制对比 spark 有什么不同和优势?
解答:spark streaming 的 checkpoint 仅仅是针对 driver 的故障恢复做了数据和元数据的 checkpoint。而 flink 的 checkpoint 机制 要复杂了很多,它采用的是轻量级的分布式快照,实现了每个算子的快照,及流动中的数据的快照。 文章链接: https://cloud.tencent.com/developer/article/1189624
问题:请详细解释一下 Flink 的 Watermark 机制。
解答:Watermark 本质是 Flink 中衡量 EventTime 进展的一个机制,主要用来处理乱序数据。
问题:Flink 中 exactly-once 语义是如何实现的,状态是如何存储的?
解答:Flink 依靠 checkpoint 机制来实现 exactly-once 语义,如果要实现端到端的 exactly-once,还需要外部 source 和 sink 满足一定的条件。状态的存储通过状态后端来管理,Flink 中可以配置不同的状态后端。
问题:Flink CEP 编程中当状态没有到达的时候会将数据保存在哪里?
解答:在流式处理中,CEP 当然是要支持 EventTime 的,那么相对应的也要
支持数据的迟到现象,也就是 watermark 的处理逻辑。CEP 对未匹配成功的事件序列的处理,和迟到数据是类似的。在 Flink CEP 的处理逻辑中,状态没有满足的和迟到的数据,都会存储在一个 Map 数据结构中,也就是说,如果我们限定判断事件序列的时长为 5 分钟,那么内存中就会存储 5 分钟的数据,这在我看来,也是对内存的极大损伤之一。
问题:Flink 三种时间语义是什么,分别说出应用场景?
解答:
问题:Flink 程序在面对数据高峰期时如何处理?
解答:使用大容量的 Kafka 把数据先放到消息队列里面作为数据源,再使用
Flink 进行消费,不过这样会影响到一点实时性