1, flink和spark有什么区别?flink的优势体现在什么地方?
2, flink的checkpoint是怎么实现的?
3, flink on yarn的启动流程?
4, flink如何实现端到端的exactly-once?
5, 谈谈你对flink状态的认识?
6, 怎么合理的配置flink任务的资源?
7, flink的反压是怎么实现的?
8, flink的watermark是干什么的?具体怎么用?
9, flink的延迟高,怎么调优?
10, flink的双流join是怎么实现的?
➤ 深入原理-Flink 数据交换和 Redistribute 详解?
➤ 深入原理-Flink 执行计划 (Shuffle)?
➤ 深入理解-Flink 内存模型?
在本文中,分为以下几个部分:
第一部分:Flink 中的核心概念和基础篇,包含了 Flink 的整体介绍、核心概念、算子等考察点。
第二部分:Flink 进阶篇,包含了 Flink 中的数据传输、容错机制、序列化、数据热点、反压等实际生产环境中遇到的问题等考察点。
第三部分:Flink 源码篇,包含了 Flink 的核心代码实现、Job 提交流程、数据交换、分布式快照机制、Flink SQL 的原理等考察点。
➤ Flink 相比传统的 Spark Streaming 有什么区别?
这个问题是一个非常宏观的问题,因为两个框架的不同点非常之多。但是在面试时有非常重要的一点一定要回答出来:Flink 是标准的实时处理引擎,基于事件驱动。而 Spark Streaming 是微批(Micro-Batch)的模型
。
下面我们就分几个方面介绍两个框架的主要区别:
- 架构模型
- Spark Streaming 在运行时的主要角色包括:Master、Worker、Driver、Executor。
- Flink 在运行时主要包含:Jobmanager、Taskmanager和Slot。
- 任务调度
- Spark Streaming 连续不断的生成微小的数据批次,构建有向无环图DAG,Spark Streaming 会依次创建 DStreamGraph、JobGenerator、JobScheduler。
- Flink 根据用户提交的代码生成 StreamGraph,经过优化生成 JobGraph,然后提交给 JobManager进行处理,JobManager 会根据 JobGraph 生成 ExecutionGraph,ExecutionGraph 是 Flink 调度最核心的数据结构,JobManager 根据 ExecutionGraph 对 Job 进行调度。
- 时间机制
- Spark Streaming 支持的时间机制有限,只支持
处理时间
。 - Flink 支持了流处理程序在时间上的三个定义:处理时间、事件时间、注入时间。同时也支持 watermark 机制来处理滞后数据。
- 容错机制
- 对于 Spark Streaming 任务,我们可以设置 checkpoint,然后假如发生故障并重启,我们可以从上次 checkpoint 之处恢复,但是这个行为只能使得数据不丢失,可能会重复处理,不能做到恰好一次处理语义。
- Flink 则使用两阶段提交协议来解决这个问题。
- 维表join和异步IO
Structured Streaming不直接支持与维表的join操作,但是可以使用map、flatmap及udf等来实现该功能,所有的这些都是同步算子,不支持异步IO操作。但是Structured Streaming直接与静态数据集的join,可以也可以帮助实现维表的join功能,当然维表要不可变。
Flink支持与维表进行join操作,除了map,flatmap这些算子之外,flink还有异步IO算子,可以用来实现维表,提升性能。
➤ 为什么说flink统一了流和批处理?
因为flink无论是批处理还是流处理,底层都是有状态的流处理,flink执行批处理实际上是流处理的一种特例,只不过此时的流式有界的,而流处理的流式无界的,应用于流处理上的transformation完全可以应用在batch上并且table API和sql都可以用在批处理和流处理上只不过区别在于
a. 容错并不是采用的流式处理的checkpoint,而是直接重新计算
b. dataset api处理的数据是很简单的数据结构,而stream处理的是key/value
c. 流处理在应用transformation和table api和sql的时候不支持topN、limit、sort普通字段等操作
另外从计算模型上来说:批处理每个stage只有完全处理完才会把缓存中(缓存+磁盘)序列化的数据发往下一个stage,而流处理是一条一条,批处理吞吐量大,流处理时效性强,而flink则是采用了折中的方式,在内存中划分缓冲小块,当小块满了就发往下一个stage。如果缓存块无限大,那么就是批处理了。
➤ Flink是如何支持批流一体的?
本道面试题考察的其实就是一句话:Flink的开发者认为**批处理
是流处理
**的一种特殊情况。批处理是有限的流处理。Flink 使用一个引擎支持了DataSet API 和 DataStream API。
➤ 你们的Flink集群规模多大?
大家注意,这个问题看起来是问你实际应用中的Flink集群规模,其实还隐藏着另一个问题:Flink可以支持多少节点的集群规模?
在回答这个问题时候,可以将自己生产环节中的集群规模、节点、内存情况说明,同时说明部署模式(一般是Flink on Yarn),除此之外,用户也可以同时在小集群(少于5个节点)和拥有 TB 级别状态的上千个节点上运行 Flink 任务。
➤ Flink的基础编程模型了解吗?
上图是来自Flink官网的运行流程图。通过上图我们可以得知,Flink 程序的基本构建是数据输入来自一个 Source,Source 代表数据的输入端,经过 Transformation 进行转换,然后在一个或者多个Sink接收器中结束。数据流(stream)就是一组永远不会停止的数据记录流,而转换(transformation)是将一个或多个流作为输入,并生成一个或多个输出流的操作。执行时,Flink程序映射到 streaming dataflows,由流(streams)和转换操作(transformation operators)组成。
➤ Flink集群有哪些角色?各自有什么作用?
Flink 程序在运行时主要有 TaskManager,JobManager,Client三种角色。
-
JobManager扮演着集群中的管理者Master的角色,它是整个集群的协调者,负责接收
Flink Job
,协调检查点
,Failover 故障恢复
等,同时管理Flink集群中从节点TaskManager。
a. JobManager 接收待执行的 application。application 包含一个 JobGraph 和 JAR (包含所有需要的classes,libraries 和其他资源)。
b. JobManager 将 JobGraph 转成 ExecutionGraph,ExecutionGraph中包含可以并发执行的 tasks。
c. JobManager 向 ResourceManager 申请需要的资源(TaskManager slots),一旦分配到足够的slots,则分发 tasks 到 TaskManager 执行。
d. 执行期间,JobManager 负责中央协调,如协调checkpoint等 -
TaskManager是实际负责执行计算的Worker,在其上执行Flink Job的一组Task,每个TaskManager负责管理其所在节点上的资源信息,如内存、磁盘、网络,在启动的时候将资源的状态向JobManager汇报。
a. 启动之后,TaskManager 向 ResourceManager 注册 slots 数,当接收到 ResourceManager 的分配通知后,会向 JobManager 提供一个或多个slots
b. 紧接着 JobManager 将 tasks 分配到 slots 执行。
c. 执行期间,不同的 TaskManager 之间会进行数据交换 -
Client是Flink程序提交的客户端,当用户提交一个Flink程序时,会首先创建一个Client,该Client首先会对用户提交的Flink程序进行预处理,并提交到Flink集群中处理,所以Client需要从用户提交的Flink程序配置中获取JobManager的地址,并建立到JobManager的连接,将Flink Job提交给JobManager。
➤ Flink的架构?
主从结构 Jobmanager,taskmanager两个进程(可以把client也加进去)。
集群模式:standalone,on yarn(在yarn上运行一个flink集群/提交到yarn上运行flink job)
Jobmanager: 作用
- registerTaskManager:在flink集群启动时,taskmanager会向jobmanager注册
- submitjob:flink程序内部通过client向jobmanager提交job,job是以jobgraph形式提交
- canceljob:请求取消一个flinkjob
- updateTaskExcutionStage:更新taskmanager中excution的状态信息
- requestnextinputsplit:运行在taskmanager上的task请求获取下一个要处理的split
- jobstatuschanged:executionGraph向jobmanager发送该消息,用来表示job的状态变化
Taskmanager: 作用
- 注册:向jobmnager注册自己
- 可操作阶段:该阶段taskmanager可以接受并处理与task有关的消息
client: 作用
client对用户提交的代码进行预处理,client将程序组装成一个 jobgraph,它是由多个jobvertex组成的DAG。
关于flink生成dag、提交job、分发task等细节 在任务提交面试题会整理。
➤ Flink 的组件栈有哪些?
根据 Flink 官网描述,Flink 是一个分层架构的系统,每一层所包含的组件都提供了特定的抽象,用来服务于上层组件。
自下而上,每一层分别代表:
- Deploy 层:该层主要涉及了Flink的部署模式,在上图中我们可以看出,Flink 支持包括local、Standalone、Cluster、Cloud等多种部署模式。
- Runtime 层:Runtime层提供了支持 Flink 计算的核心实现,比如:支持分布式 Stream 处理、JobGraph到ExecutionGraph的映射、调度等等,为上层API层提供基础服务。
- API层:API 层主要实现了面向流(Stream)处理和批(Batch)处理API,其中面向流处理对应DataStream API,面向批处理对应DataSet API,后续版本,Flink有计划将DataStream和DataSet API进行统一。
- Libraries层:该层称为Flink应用框架层,根据API层的划分,在API层之上构建的满足特定应用的实现计算框架,也分别对应于面向流处理和面向批处理两类。面向流处理支持:CEP(复杂事件处理)、基于SQL-like的操作(基于Table的关系操作);面向批处理支持:FlinkML(机器学习库)、Gelly(图处理)。
➤ JobManger在集群中扮演了什么角色?
JobManager 负责整个 Flink 集群任务的调度
以及资源的管理
,从客户端中获取提交的应用,然后根据集群中 TaskManager 上 TaskSlot 的使用情况,为提交的应用分配相应的 TaskSlot 资源并命令 TaskManager 启动从客户端中获取的应用。
- JobManager 相当于整个集群的 Master 节点,且整个集群有且只有一个活跃的 JobManager ,负责整个集群的任务管理和资源管理。
- JobManager 和 TaskManager 之间通过 Actor System 进行通信,获取任务执行的情况并通过 Actor System 将应用的任务执行情况发送给客户端。
- 同时在任务执行的过程中,Flink JobManager 会触发 Checkpoint 操作,每个 TaskManager 节点 收到 Checkpoint 触发指令后,完成 Checkpoint 操作,所有的 Checkpoint 协调过程都是在 Fink JobManager 中完成。
- 当任务完成后,Flink 会将任务执行的信息反馈给客户端,并且释放掉 TaskManager 中的资源以供下一次提交任务使用。
➤ JobManger在集群启动过程中起到什么作用?
JobManager的职责主要是接收Flink作业,调度Task,收集作业状态和管理TaskManager。它包含一个Actor,并且做如下操作:
- RegisterTaskManager: 它由想要注册到JobManager的TaskManager发送。注册成功会通过AcknowledgeRegistration消息进行Ack。
- SubmitJob: 由提交作业到系统的Client发送。提交的信息是JobGraph形式的作业描述信息。
- CancelJob: 请求取消指定id的作业。成功会返回CancellationSuccess,否则返回CancellationFailure。
- UpdateTaskExecutionState: 由TaskManager发送,用来更新执行节点(ExecutionVertex)的状态。成功则返回true,否则返回false。
- RequestNextInputSplit: TaskManager上的Task请求下一个输入split,成功则返回NextInputSplit,否则返回null。
- JobStatusChanged: 它意味着作业的状态(RUNNING, CANCELING, FINISHED,等)发生变化。这个消息由ExecutionGraph发送。
➤ TaskManager在集群中扮演了什么角色?
TaskManager 相当于整个集群的 Slave 节点,负责具体的任务执行
和对应任务在每个Node上的资源申请
和管理
。
- 客户端通过将编写好的 Flink 应用编译打包,提交到 JobManager,然后 JobManager 会根据已注册在 JobManager 中 TaskManager 的资源情况,将任务分配给有资源的 TaskManager节点,然后启动并运行任务。
- TaskManager 从 JobManager 接收需要部署的任务,然后使用 Slot 资源启动 Task,建立数据接入的网络连接,接收数据并开始数据处理。同时 TaskManager 之间的数据交互都是通过数据流的方式进行的。
可以看出,Flink 的任务运行其实是采用多线程的方式,这和 MapReduce 多 JVM 进行的方式有很大的区别,Flink 能够极大提高 CPU 使用效率,在多个任务和 Task 之间通过 TaskSlot 方式共享系统资源,每个 TaskManager 中通过管理多个 TaskSlot 资源池进行对资源进行有效管理。
➤ TaskManager在集群启动过程中起到什么作用?
TaskManager的启动流程较为简单:
启动类:org.apache.flink.runtime.taskmanager.TaskManager
核心启动方法 : selectNetworkInterfaceAndRunTaskManager 启动后直接向JobManager注册自己,注册完成后,进行部分模块的初始化。
➤ flink中应用在tableAPI中的UDF有几种?
- scalar function:针对一条record的一个字段的操作,返回一个字段
- table function:针对一条record的一个字段的操作,返回多个字段
- aggregate function:针对多条记录的一个字段操作,返回一条记录
➤ 你知道UDF吧,请问我们注册UDF到底是每个计算线程一份还是每个executor一份?或者说是多个对象还是共享一个对象?如果答的对的话,面试官会问你如何保证共享呢,这就涉及单例对象的问题了。这个问题有点乱,请自行整理。(头条)
我们要知道一般来说在使用一个类的时候,一般是要创建对象的,所以我们在sql里使用UDF的时候会创建对象,如果是多线程并行操作sql,那么就是多个UDF对象。那么如何保证一个executer进程中共享一个UDF呢,在scala中就用Object即可。如果是class就写一个单例模式,关于单例模式算法题中我会详细整理!
➤ 你知道flink可以修改代码恢复吧!但是不是所有的修改都可以恢复哦,请问什么样的代码修改会导致无法flink任务恢复?(头条)
面试官说:只有当不会改变DAG的修改才会正常恢复!!!有机会试一下。
➤ Flink 的运行必须依赖 Hadoop组件吗?
Flink可以完全独立于Hadoop,在不依赖Hadoop组件下运行。但是做为大数据的基础设施,Hadoop体系是任何大数据框架都绕不过去的。Flink可以集成众多Hadooop 组件,例如Yarn、Hbase、HDFS等等。例如,Flink可以和Yarn集成做资源调度,也可以读写HDFS,或者利用HDFS做检查点。
➤ Flink是如何做容错的?
Flink 实现容错主要靠强大的 CheckPoint机制
和 State机制
。
- Checkpoint 负责定时制作分布式快照、对程序中的 State 状态进行备份;
- State 用来存储计算过程中的中间状态。
➤ flink的 Checkpoint 机制详细讲一下?注意与spark的区别?
flink是通过checkpoint机制实现容错,它的原理是不断的生成分布式streaming数据流snapshot快照。在流处理失败时通过这些snapshot可以恢复数据流处理。而flink的快照有两个核心:
barrier 机制
:barrier是实现checkpoint的机制。state 状态保存
:state保存则是通过barrier这种机制进行分布式快照
的实现。
1. barrier
barrier是checkpoint的核心,他会当做记录
打入数据流
,从而将数据流分组
,并沿着数据流方向向前推进
,每个barrier会携带一个snapshotID
,属于该snapshot的记录会被推向该barrier的前方。所以barrier之后的属于下一个ckeckpoint期间(snapshot中)的数据。然后当中间的operation接收到barrier后,会发送barrier到属于该barrier的snapshot的数据流中,等到sink operation接收到该barrier后会向checkpoint coordinator确认该snapshot,直到所有的sink operation都确认了该snapshot,才会认为完成了本次checkpoint或者本次snapshot。
理解:可以认为barrier这种机制是flink实现分布式快照的手段。那么在这个过程中要记录state快照信息,到底有哪些信息需要序列话呢?
在说state保存之前我们要知道flink的三种方式,
- jobmanager内存,不建议;
- hdfs(可以使用,
同步
进行分布式快照); - rocksDB(
异步
进行分布式快照)。
除了第3种其他两种都是同步快照。也就是说用hdfs
这种方式快照是会阻塞数据处理
的,只有当两个barrier之间数据处理完成并完成快照之后才向下一个task发送数据并打入barrier n。我们不管异步快照,我们现在只说同步快照。
2. state状态保存
state状态保存分为两种:
- 一种是
用户自定义状态
:也就是我们为了实现需求敲的代码(算子),他们来创建和修改的state; - 一种是
系统状态
:此状态可以认为数据缓冲区,比如window窗口函数,我们要知道数据处理的情况。
生成的快照现在包含:
- 对于每个并行流数据源,创建快照时流中的偏移/位置
- 对于每个运算符,存储在快照中的状态指针
3. stream aligning (barrier k对齐
)
这个情况出现的很少,用于解决同一个Operation处理多个输入流
的情况(不是同一个数据源),这种情况下operation将先收到barrier k
的数据缓存起来不进行处理,只有当另一个流的barrier k
到达之后再进行处理,同时opeartion会向checkpoint coordinator
上报snapshot。这就是barrier k对齐
。
SparkCheckpoint
spark的checkpoint的方式没有这么复杂,直接通过记录metadata和data的方式来进行checkpoint。从checkpoint中恢复时ss是决不允许修改代码的,而sss是有些情况可以接受修改代码的。
a. metadata checkpoint
将定义流式计算的信息保存到hdfs:配置、dstream操作、尚未完成的批次
b. data checkpoint
这就比较直接了,直接持久化RDD到hdfs,因为我们知道spark的容错就是基于rdd的血缘关系的,而为了避免依赖关系链太长,spark会定期从最新的rdd中持久化数据到hdfs。
注意:::如果spark程序中没有updateStateByKey或reduceByKeyAndWindow这种带有状态持续改变的算子操作的时候完全可以不用对rdd进行持久化,只需要利用metadata来恢复程序即可,因为数据的丢失时可以接受的,但是如果存在状态转换的算法就不行了。
➤ Flink 分布式快照 Checkpoint 的原理是什么?
Flink的分布式快照是根据Chandy-Lamport算法量身定做的。简单来说就是持续创建分布式数据流
及其状态
的一致快照。
核心思想:是在 input source 端插入 barrier
,控制 barrier 的同步来实现 snapshot 的备份 和 exactly-once 语义。
➤ Flink 是如何保证Exactly-once语义的?
Flink通过实现 两阶段提交
和 状态保存
来实现 端到端
的一致性语义。 分为以下几个步骤:
- 开始事务(beginTransaction)创建一个临时文件夹,来写把数据写入到这个文件夹里面
- 预提交(preCommit)将内存中缓存的数据写入文件并关闭
- 正式提交(commit)将之前写完的临时文件放入目标目录下。这代表着最终的数据会有一些延迟
- 丢弃(abort)丢弃临时文件
自己整理的版本
- 使用 支持 Exactly-Once 数据源
- 使用 FlinkKafkaConsumer, 开启 checkpointing,把偏移量通过 checkpoint 机制保存到 StateBackend 中,并且默认会将偏移量写入到 Kafka __consumer_offset Topic 中。
- FlinkKafkaConsumer.setCommitOffsetsOnCheckpoints 默认为 true,即将偏移量写入到 Kafka __consumer_offset 中,目的是为了监控或重启任务时,没有指定 savepoint 可以接着一起的偏移量继续消费。
- 设置 CheckpointingMode.EXACTLY_ONCE
- 存储系统支持覆盖(Redis, Hbase, ES) 使用幂等性,覆盖原来的数据。
- Barrier 可以保证一个流水线中所有算子都处理完了,再对该条数据做 Checkpoint.
- 存储系统不支持覆盖,要支持事务,成功:提交事务,更新偏移量;失败:回滚事务,不更新偏移量,放弃这次 Checkpoint ,标记为失败状态,继续下一次Checkpoint。
a. 特点是:可以保证提交事务和 Checkpointing 同时成功。
若失败发生在预提交成功后,正式提交前。可以根据状态来提交预提交的数据,也可删除预提交的数据。
➤ 请问Flink到底是如何保证端到端的exactly once语义的?请从source——算子——算子——sink整个流程说明。可以从kafka的sink或者producer说起。需要注意的是ckeckpoint和offset提交的先后顺序,可以看一下源码。貌似与flink的两端提交有关。
可以从两方面阐述:
第一:flink的checkpoint机制可以保证at least once消费语义
第二:flink的两段式提交commit保证了端对端的exactly once消费语义(TwoPhaseCommitSinkFunction)
尤其是在kafka0.11版本开始,支持两段式提交
Flink1.4之前只能在flink内存保证exactly once语义,但是很多时候flink要对接其他系统,那么就要实现commit提交和rollback回滚机制,而分布式系统中两段提交和回滚就是实现方式。因为很多算子包括sink都是并行的,我们不能通过sink的一次commit就完成了最终的commit,因为假如有10的sink,其中9个sink commit了第十个失败了,那么这个过程我们还是无法回滚!!所以需要分布式两段提交策略。
2阶段提交的思想?
- pre-commit
- commit,
所谓pre-commit指的是第一阶段,也就是checkpoint阶段完成时进行pre-commit,如果所有的pre-commit成功,jobmanager会通知所有跟外部系统有联系的比如sink,通知他们进行第二阶段的commit!这就是两段式提交实现的flink的exactly once消费语义。
➤ 为什么要开启 Checkpoint?
开启 Checkpoint 机制主要是为了实现 实时任务处理的容错
。
实时任务不同于批处理任务,除非用户主动停止,一般会一直运行,运行的过程中可能存在机器故障、网络问题、外界存储问题等等,要想实时任务一直能够稳定运行,实时任务要有自动容错恢复的功能。
而批处理任务在遇到异常情况时,在重新计算一遍即可。实时任务因为会一直运行的特性,如果在从头开始计算,成本会很大,尤其是对于那种运行时间很久的实时任务来说。
实时任务开启 Checkpoint 功能,也能够减少容错恢复的时间。因为每次都是从最新的 Chekpoint 点位开始状态恢复,而不是从程序启动的状态开始恢复。
➤ Flink Checkpoint 常见失败原因分析?
Flink Checkpoint 失败有很多种原因,常见的失败原因如下:
- 用户代码逻辑没有对于异常处理,让其直接在运行中抛出。比如解析 Json 异常,没有捕获,导致 Checkpoint失败,或者调用 Dubbo 超时异常等等。
- 依赖外部存储系统,在进行数据交互时,出错,异常没有处理。比如输出数据到 Kafka、Redis、HBase等,客户端抛出了超时异常,没有进行捕获,Flink 任务容错机制会再次重启。
- 内存不足,频繁GC,超出了 GC 负载的限制。比如 OOM 异常
- 网络问题、机器不可用问题等等。
从目前的具体实践情况来看,Flink Checkpoint 异常觉大多数还是用户代码逻辑的问题,对于程序异常没有正确的处理导致。所以在编写 Flink 实时任务时,一定要注意处理程序可能出现的各种异常。这样,也会让实时任务的逻辑更加的健壮。
➤ Flink Checkpoint 太频繁,或时间太久会有什么影响?
checkpoint 的执行间隔要根据实际业务情况配置,checkpoint次数据太频繁,容易给后端系统造成压力。checkpoint 间隔时间太久,状态数据恢复时间较长。
➤ Flink checkpoint 成功后,会不会删除之前的 checkpoint 数据?Flink 会生成多少个 checkpoint文件?
checkpoint执行成功后,会自动删除之前保存的 checkpoint 数据。有多少个 SubTask就会生成多少个 checkpoint 文件。每个 SubTask 保存自己的数据。
➤ Flink的 BackPressure 背压机制,跟spark有什么区别?
- Flink是通过
自下而上
的背压检测
从而 控制流量。如果下层的operation压力大那么上游的operation就会让它慢下来。Jobmanager会反复调用一个job的task运行所在线程的Thread.getStackTrace(),默认情况下,jobmanager会每个50ms触发对一个job的每个task依次进行100次堆栈跟踪调用,根据调用结果来确定backpressure,flink是通过计算得到一个比值radio来确定当前运行的job的backpressure状态。在web页面可以看到这个radio值,它表示在一个内部方法调用中阻塞