基础篇
- 介绍一下Flink
Flink是大数据领域下的分布式实时和离线计算引擎,其程序的基础构建模块是流(stream)和转换(transformation),每一个数据流起始于一个或多个source,并终止于一个或多个sink,数据流流向类似于一个有向无环图。
Flink提供了诸多高抽象层的API,以便于编写分布式任务:
- DataSetAPI,对静态数据进行批处理操作,将静态数据抽象成分布式的数据集,用户可以方便地使用 Flink 提供的各种操作符对分布式数据集进行处理,支持 Java、Scala 和 Python;
- DataStreamAPI,对数据流进行流处理操作,将流式的数据抽象成分布式的数据流,用户可以方便地对分布式数据流进行各种操作,支持 Java 和 Scala;
- TableAPI,对结构化数据进行查询操作,将结构化数据抽象成关系表,并通过类 SQL 的 DSL 对关系表进行各种查询操作,支持 Java 和 Scala。
- 此外,Flink针对其他领域,提供了机器学习Pipelines API,并实现了多种机器学习算法,以及图计算领域的相关API和算法的实现。
- Flink的主要特性是什么?
Flink的主要特性包括:
- 批流一体
- Exactly-Once语义
- 强大的状态管理
- 支持运行在Yarn,Mesos,Kubernetes等资源管理框架上,Flink 可以扩展到数千核心,其状态可以达到 TB 级别,且仍能保持高吞吐、低延迟的特性。
- Flink的适用场景有哪些?
Flink的主要应用场景:
- 实时数据计算
- 实时数据仓库
- 实时ETL处理
- 事件驱动型场景,如告警,监控
- 机器学习和人工智能领域的计算引擎
- Flink和Spark Streaming异同点有哪些?
Flink 是标准的实时处理引擎,基于事件驱动;而 Spark Streaming 是微批(Micro-Batch)的模型。
Flink 的优势及与其他框架的区别:
- 架构
- Spark Streaming 的架构是基于 Spark 的,它的本质是微批处理,每个 batch 都依赖 Driver,我们可以把 Spark Streaming 理解为时间维度上的 Spark DAG。
- Flink 也采用了经典的主从模式,DataFlow Graph 与 Storm 形成的拓扑 Topology 结构类似,Flink 程序启动后,会根据用户的代码处理成 Stream Graph,然后优化成为 JobGraph,JobManager 会根据 JobGraph 生成 ExecutionGraph。ExecutionGraph 才是 Flink 真正能执行的数据结构,当很多个 ExecutionGraph 分布在集群中,就会形成一张网状的拓扑结构。
- 容错
- Spark Streaming,配置了对应的保存点(checkpoint),当任务失败(fail over)后,会从checkpoint进行加载,使得数据不会丢失,这个过程可能会导致原来的数据出现重复处理,不能做到一次处理的语义
- Flink基于两阶段提交实现了精确的一次处理语义
- 反压(BackPressure)
反压是分布式处理系统中经常遇到的问题,当消费者速度低于生产者的速度时,则需要消费者将信息反馈给生产者使得生产者的速度能和消费者的速度进行匹配。
- Spark Streaming 为了实现反压这个功能,在原来的架构基础上构造了一个“速率控制器”,“速率控制器”会根据几个属性,比如任务的结束时间、处理时长、处理消息的条数等计算一个速率。在实现控制数据的接收速率中用到了一个经典的算法,即“PID 算法”。
- Flink 没有使用任何复杂的机制来解决反压问题,Flink 在数据传输过程中使用了分布式阻塞队列。我们知道在一个阻塞队列中,当队列满了以后发送者会被天然阻塞住,这种阻塞功能相当于给这个阻塞队列提供了反压的能力。
- 时间机制
- Spark Streaming 支持的时间机制有限,只支持处理时间。
- Flink 支持了流处理程序在时间上的三个定义:处理时间、事件时间、注入时间;同时也支持 watermark 机制来处理滞后数据。
- 谈一谈Flink的组件栈和数据流模型
- Flink 自身提供了不同级别的抽象来支持我们开发流式或者批量处理程序,以下是支持的 4 种不同级别的抽象:
- 最低级别的 Low-level 抽象
它允许用户可以自由地处理来自一个或多个数据流的事件,并使用一致的容错的状态。用户可以注册事件时间并处理时间回调,从而使程序可以处理复杂的计算。
- Core APIS
主要包括 DataStream API(有界或无界数据流)和DataSet API(有界数据集),这些API为数据集生成了通用的构建板块,比如由用户定义的多种形式的转换(transformations),连接(joins),聚合(aggregations),窗口操作(windows),状态(state)等等。这些API处理的数据类型以类(classes)的形式由各自的编程语言所表示。
底层过程函数(Process Function) 与 DataStream API 相集成,使其可以对某些特定的操作进行底层的抽象。DataSet API 为有界数据集提供了额外的原语,例如循环与迭代。
- Table API
是以表为中心的声明式DSL,其中表可能会动态变化(在表达流数据时)。
Table API遵循(扩展的)关系模型:表有二维数据结构(schema)(类似于关系数据库中的表),同时API提供可比较的操作,例如select、project、join、group-by、aggregate等。Table API程序声明式地定义了“什么逻辑操作应该执行”而不是准确地确定,除此之外,Table API程序在执行之前会经过内置优化器进行优化
- SQL
Flink提供的最高层级的抽象是SQL ,和Table API类似,SQL抽象与Table API交互密切,同时SQL查询可以直接在Table API定义的表上执行。简化了实时计算的计算模型,降低了用户使用实时计算门槛。
- 数据流模型
Flink 程序的基本构建是数据输入来自一个 Source,Source 代表数据的输入端,经过 Transformation 进行转换,然后在一个或者多个 Sink 接收器中结束。数据流(Stream)就是一组永远不会停止的数据记录流,而转换(Transformation)是将一个或多个流作为输入,并生成一个或多个输出流的操作。在执行时,Flink 程序映射到 Streaming Dataflows,由流(Streams)和转换操作(Transformation Operators)组成。
- 谈一谈Flink中的角色和作用各是什么?
Flink遵循Master-Slave架构设计原则,JobManager为Master节点,TaskManager为Worker节点,组件之间的通信是通过Akka 来实现,包括任务的状态以及CheckPoint触发信息。
其中涉及的角色主要涉及到三个部分:Client、JobManager、TaskManager
- Client客户端
客户端负责将任务提交到集群上面,Client与JobManager构建Akka连接,然后将任务提交到JobManager,通过Client和JobManager之间进行交互获取任务执行状态。客户端提交任务可以采用Flink WEB UI 的方式,也可以采用命令行的方式进行提交。
- JobManager
jobManager负责整个Flink 集群任务的调度以及资源的管理,从客户端中获取提交的应用,然后根据集群中的TaskManager上面TaskSlot的使用情况,为提交的应用分配相应的TaskSlots 资源并命令TaskManager启动从客户端中获取的应用,JobManager相当于整个集群的Master节点,整个集群只有一个active状态的Jobmanager,负责整个集群的资源管理和任务管理,JobManager和TaskManager之间通过Actor System进行通信获取任务执行的情况,并通过Actor System将应用的执行情况发送给客户端。同时在任务的执行过程中Flink JobManager会触发Checkpoints 操作,每个Taskmanager 节点收到CheckPoint 触发指令后,完成CheckPoint操作,所有的CheckPoint协调过程都是在Flink JobManager 中完成,任务结束之后Flink会将任务执行的信息反馈给客户端,并且释放掉TaskManager的资源供下一次的提交使用。
- Taskmanager
TaskManager相当于整个集群的Slave节点,负责具体的任务执行和对应任务在每个节点上的资源申请与管理,客户端通过将编写好的Flink应用编译打包,提交到JobManager,然后Jobmanager 会根据已经注册在JobManager中的资源情况,将任务分配给有资源的TaskManager节点,然后启动并运行任务,Taskmanager从Jobmanager接收需要部署的任务,然后使用Slot资源启动Task,建立数据接入的网络连接,接收数据并开始数据处理,同时TaskManager 之间的数据交互都是通过数据流的方式进行的。
- 谈一谈Flink中的计算资源TaskSlot
- 在 Flink 集群中,一个 TaskManger 就是一个 JVM 进程,并且会用独立的线程来执行 Task,为了控制一个 TaskManger 能接受多少个 Task,Flink 提出了 Task Slot 的概念。
- TaskSlot是Flink中最小的资源管理单元,它仅会均分TaskManager上的内存,但不隔离CPU。如果每个TaskManager上面有3个TaskSlot,那么意味着每个TaskSlot有三分之一TaskManage内存。
- 通过调整slot的数量,可以控制子任务的隔离程度。例如:如果每个TaskManager只有1个slot,那么每个子任务都运行在单独的JVM进程中;每个TaskManager有多个slot的话,就意味着可以有更多的子任务运行在同一个JVM进程中。而在同一个JVM进程中的子任务,可以共享上下文信息、TCP连接和心跳消息,减少数据的网络传输,也能共享一些数据结构,一定程度上减少了每个子任务的消耗。
- 由于TaskSlot并不隔离CPU,因此一般情况上可以配置TaskSlot的个数等于CPU核数+1,充分利用线程。
- TaskSlot是可以共享的,同一个Job的多个Task,其中几个Task是可以共享使用同一个TaskSlot的,这种共享方式主要有两个优点:
- 能更好的利用资源。如果没有slot共享,那些资源需求不大的子任务和资源需求大的子任务会占用相同的资源,但如允许slot共享,它们就可能被分配到同一个slot中;
- Flink计算一个Job所需的slot数量时,只需要确定其最大并行度即可,而不用考虑每一个任务的并行度;
- 怎样在Flink中的设置并行度?
Flink并行度的设置级别:
- 算子级别
- 环境级别
- 客户端级别
- 集群配置级别
- 谈一谈Flink支持的集群规模情况。
集群规模
配置情况
Flink版本
部署模式(Flink on Yarn)
- 谈一谈Flink中的时间分类。
Flink中的时间分为三种:
- 事件时间(Event Time),即事件实际发生的时间
- 摄入时间(Ingestion Time),事件进入流处理框架的时间
- 处理时间(Processing Time),事件被处理的时间
- 谈一谈Flink中的水印(WaterMark)
水印的出现是为了解决实时计算中的数据乱序问题,本质是DataStream中一个带有时间戳的元素。
- 谈一谈Flink中的窗口
Flink支持三种窗口:
- 浮动窗口,窗口数据有固定的大小
- 滑动窗口,窗口数据有固定的大小
- 会话窗口,窗口数据没有固定的大小
进阶篇
- 谈一谈你对Flink Table & SQL的了解情况? TableEnvironment 这个类有什么样的作用?
TableEnvironment 是Table API和SQL集成的核心概念,主要被用来创建Table & SQL程序的上下文执行环境。这个类主要用来:
- 在内部catalog中注册表
- 注册外部catalog
- 执行SQL查询
- 注册用户定义(标量、表或聚合)函数
- 将DataStream 或 DataSet 转化为表
- 持有对 ExecutionEnvironment 或 StreamExecutionEnvironment 的引用
- Flink SQL 的原理是什么?请谈谈整个SQL的解析过程。
完整的 Flink SQL 程序,通过客户端提交到Flink集群后,其解析过程如下:
- 用户使用对外提供的 Stream SQL 语法来开发业务应用;
- 用 calcite 对 StreamSQL 进行语法检验,语法检验通过后,转换成 calcite 的逻辑树节点,最终形成 calcite 的逻辑计划;
- 采用 Flink 自定义的优化规则和 calcite 火山模型、启发式模型共同对逻辑树进行优化,生成最优的 Flink 物理计划;
- 对物理计划采用 janino codegen 生成代码,生成用低阶 API DataStream 描述的流应用,并提交到 Flink 平台执行。
- 请谈谈你对 Flink 容错的理解
Flink 的容错机制是非常重要的一个特性,我们在回答这个问题时,要把 CheckPoint 和 State 都答出来,并且阐述一下原理。
Flink 实现容错主要靠强大的 CheckPoint 和 State 机制。Checkpoint 负责定时制作分布式快照、对程序中的状态进行备份;State 用来存储计算过程中的中间状态。
Flink 提供了三种可用的状态后端用于在不同情况下进行状态后端的保存:
MemoryStateBackend
FsStateBackend
RocksDBStateBackend
- Flink 是如何保证 Exactly-once 语义的
Flink 的“精确一次”语义的支持是区别于其他框架最显著的特性之一。
Flink 通过实现两阶段提交和状态保存来实现端到端的一致性语义,分为以下几个步骤:
- 开始事务(beginTransaction),创建一个临时文件夹,然后把数据写入这个文件夹里面;
- 预提交(preCommit),将内存中缓存的数据写入文件并关闭;
- 正式提交(Commit),将之前写完的临时文件放入目标目录下,这代表着最终的数据会有一些延迟;
- 丢弃回滚(Abort),丢弃临时文件。
- 请谈谈 Flink 中的内存管理
Flink 的内存管理的准确掌握也是我们进行内存调优和配置的前提。
Flink 并不是将大量对象存在堆上,而是将对象都序列化到一个预分配的内存块上,此外,Flink 大量使用了堆外内存。如果需要处理的数据超出了内存限制,则会将部分数据存储到硬盘上。
Flink 为了直接操作二进制数据实现了自己的序列化框架。
理论上 Flink 的内存管理分为以下 3 部分。
- Network Buffers:这个是在 TaskManager 启动的时候分配的,这是一组用于缓存网络数据的内存,每个块是 32K,默认分配 2048 个,可以通过“taskmanager.network.numberOfBuffers”修改。
- Memory Manage pool:大量的 Memory Segment 块,用于运行时的算法(Sort/Join/Shuffle 等),这部分启动时会被分配。根据配置文件中的各种参数来计算内存的分配方法,并且内存的分配支持预分配和 lazy load,默认懒加载的方式。
- User Code,这个是除了 Memory Manager 之外的内存用于 User Code 和 TaskManager 本身的数据结构。
- 请谈谈你遇到的数据倾斜问题?Flink 中的 Window 出现了数据倾斜,你有什么解决办法?
数据倾斜是大数据领域最常见的问题之一。Flink 中对于数据倾斜的调优极其重要,我们一定要掌握。
产生数据倾斜的原因主要有 2 个方面:
- 业务上有严重的数据热点,比如滴滴打车的订单数据中北京、上海等几个城市的订单量远远超过其他地区;
- 技术上大量使用了 KeyBy、GroupBy 等操作,错误的使用了分组 Key,人为产生数据热点。
因此解决问题的思路也很清晰:
- 业务上要尽量避免热点 key 的设计,例如我们可以把北京、上海等热点城市分成不同的区域,并进行单独处理;
- 技术上出现热点时,要调整方案打散原来的 key,避免直接聚合;此外 Flink 还提供了大量的功能可以避免数据倾斜。
另外我们还可以结合实际工作中出现的问题做举例说明。
- Flink 任务出现很高的延迟,你会如何入手解决类似问题?
这是一个实操性很强的问题,我们在回答这类问题一定要结合实际情况来说。在生产环境中处理这类问题会从以下方面入手:
- 在 Flink 的后台任务管理中,可以看到 Flink 的哪个算子和 task 出现了反压;
- 资源调优和算子调优,资源调优即对作业中的 Operator 并发数(Parallelism)、CPU(Core)、堆内存(Heap_memory)等参数进行调优;
- 作业参数调优,并行度的设置、State 的设置、Checkpoint 的设置。
- 请谈谈你们是如何处理脏数据的?
建议你结合自己的实际业务来谈。比如可以通过一个 fliter 算子将不符合规则的数据过滤出去。当然了,我们也可以在数据源头就将一些不合理的数据抛弃,不允许进入 Flink 系统参与计算。
- 请谈谈 Operator Chain 这个概念?
算子链是我们进行任务调优一定会遇到的问题,主要考察我们对于概念是否正确理解,实际操作中有怎样的指导作用。
为了更高效地分布式执行,Flink 会尽可能地将 Operator 的 Subtask 链接(Chain)在一起形成 Task,每个 Task 在一个线程中执行。将 Operators 链接成 Task 是非常有效的优化,它能减少:
- 线程之间的切换;
- 消息的序列化/反序列化;
- 数据在缓冲区的交换;
- 延迟的同时提高整体的吞吐量。
这就是我们所说的算子链。
- Flink 在什么情况下才会把 Operator Chain 在一起形成算子链?
截止 1.11 版本,Flink 的算子之间形成算子链需要以下条件:
- 上下游的并行度一致
- 下游节点的入度为 1(即下游节点没有来自其他节点的输入)
- 上下游节点都在同一个 Slot Group 中
- 下游节点的 Chain 策略为 ALWAYS(可以与上下游链接,Map、Flatmap、Filter 等默认是 ALWAYS)
- 上游节点的 Chain 策略为 ALWAYS 或 HEAD(只能与下游链接,不能与上游链接,Source 默认是 HEAD)
- 两个节点间数据分区方式是 Forward
- 用户没有禁用 Chain
- 请谈谈 Flink 中的分布式快照机制是如何实现的?
需要你完全理解 Flink 中的分布式快照机制的实现原理。
Flink 容错机制的核心部分是制作分布式数据流和操作算子状态的一致性快照,这些快照充当一致性 Checkpoint,系统可以在发生故障时回滚。Flink 用于制作这些快照的机制在“分布式数据流的轻量级异步快照”中进行了描述,它受到分布式快照的标准 Chandy-Lamport 算法的启发,专门针对 Flink 的执行模型而定制。
barrier 在数据流源处被注入并行数据流中。快照 n 的 barrier 被插入的位置(我们称为 Sn)是快照所包含的数据在数据源中最大位置。例如,在 Apache Kafka 中,此位置将是分区中最后一条记录的偏移量,将该位置 Sn 报告给 Checkpoint 协调器(Flink 的 JobManager)。
接着 barrier 向下游流动。当一个中间操作算子从其所有输入流中收到快照 n 的 barrier 时,它会为快照 n 发出 barrier 进入其所有输出流中。 一旦 sink 操作算子(流式 DAG 的末端)从其所有输入流接收到 barrier n,它就向 checkpoint 协调器确认快照 n 完成。在所有 sink 确认快照后,意味着快照已完成。
一旦完成快照 n,job 将永远不再向数据源请求 Sn 之前的记录,因为此时这些记录(及其后续记录)将已经通过整个数据流拓扑,也即已经被处理结束。
方案设计篇
- 基于 Flink 的实时数据仓库是如何做的?
我们要从 Flink 的优势开始入手,介绍基于 Flink 的实时数仓建设的关键技术选型和整体设计。
传统的离线数据仓库将业务数据集中进行存储后,以固定的计算逻辑定时进行ETL和其他建模后产出报表等应用。离线数据仓库主要是构建 T+1 的离线数据,通过定时任务每天拉取增量数据,然后创建各个业务相关的主题维度数据,对外提供 T+1 的数据查询接口。计算和数据的实时性均较差,业务人员无法根据自己的即时性需要获取几分钟之前的实时数据。数据本身的价值随着时间的流逝会逐步减弱,因此数据发生后必须尽快地达到用户的手中,实时数仓的构建需求也应运而生。
总之就是一句话:时效性的要求。
Flink 在实时数仓和实时 ETL 中有天然的优势:
- 状态管理,实时数仓里面会进行很多的聚合计算,这些都需要对状态进行访问和管理,Flink 支持强大的状态管理;
- 丰富的 API,Flink 提供了极为丰富的多层次 API,包括 Stream API、Table API 及 Flink SQL;
- 生态完善,实时数仓的用途广泛,Flink 支持多种存储(HDFS、ES 等);
- 批流一体,Flink 已经在将流计算和批计算的 API 进行统一。
我们在进行实时数仓的设计时,一般也是分为 ODS 源数据接入层、DWD 明细层、DWS 汇总层、ADM 应用层。
这其中比较关键的技术点:实时数仓的明细层的汇总一般是基于 Flink 等接入 Kafka 消息进行关联的,维度表的数据一般会放在 HDFS、HBase 中作为明细层的补充。另外,在实时数据仓库中要选择不同的 OLAP 库来满足即席查询。
此外,可以将自己的实时数据的架构图完整画出来,这样会给人很深刻的印象。
- 基于 Flink 的实时计算平台是如何做的?
实时计算平台的搭建是随着越来越多的业务场景需要实时计算而产生的。由于离线计算天然时效性不强,一般都是隔天级别的滞后,业务数据随着实践的推移,本身的价值就会逐渐减少。
由于 Flink 在架构、容错、反压上表现出来的优势和特性,使得 Flink 在实时计算平台的搭建上占有一席之地。
一般的实时计算平台的构成大都是由以下几部分构成。
- 实时数据收集层
在实际业务中,大量的实时计算都是基于消息系统进行的数据收集和投递,这都离不开强大的消息中间件。目前业界使用最广的是 Kafka,另外一些重要的业务数据还会用到其他消息系统比如 RocketMQ 等。Kafka 因为高吞吐、低延迟的特性,特别适合大数量量、高 QPS 下的业务场景,而 RocketMQ 则在事务消息、一致性上有独特的优势。
- 实时计算层
Flink 在计算层同时支持流式及批量分析应用,这就是我们所说的批流一体,Flink 承担了数据的实时采集、实时计算和下游发送的角色。随着 Blink 的开源和一些其他实时产品的开源,支持可视化、SQL 化的开发模式已经越来越普及。
- 数据存储层
这里是我们的实时数据存储层,存储层除了传统 MySQL 等存储引擎以外,还会根据场景数据的不同存储在 Redis、HBase、OLAP 中。而这一层我个人认为最重要的技术选型则是 OLAP。OLAP 的技术选型直接制约着数据存储层和数据服务层的能力。关于 OLAP 的技术选型,可以参考这里。
- 数据服务层
数据服务层会提供统一的对外查询、多维度的实时汇总,加上完善的租户和权限设计,能够支持多部门、多业务的数据需求。另外,基于数据服务层还会有数据的展示、大屏、指标可视化等。
我们在回答这个面试问题时,可以结合自己的实际项目回答,例如,在实时数据收集层中的技术选型是什么、支持了多大的数据量以及取得的业务成果;并且可以把自己的架构图完整地画出来。
- 有没有做过基于 Flink SQL 的计算平台?你有什么思路?
基于 Flink SQL 的实时计算平台是很多大公司提高开发效率、降低开发成本、降低维护成本作出的选择。一般的 SQL 开发平台都会有以下几个模块:
- SQL 开发和提交模块
- 完善的权限管理体系
- UDF 管理模块
- 资源调度模块
- 日志和监控模块
这其中,SQL 的开发和提交是核心,成熟的 SQL 开发平台应该是和公司常用的组件栈打通,基于 Flink 原生的 SQL 模块支持更为丰富的 Source 和 Sink。并且支持丰富的作业配置,如作业 Checkpoint、重启策略等。
权限管理模块中,我们的 SQL 应该在作业级别进行多租户的权限管控,对于原表和目标表更应该精确到开发者级别。
UDF 模块应该支持用户自定义基于业务的复杂 UDF,并且可以快捷方便地更新版本。
资源和调度模块可以支持算子级别的并行度和内存设置,并且应该支持作业在不同集群间进行切换。
日志和监控模块中是我们进行作业调优、异常监控非常重要的模块,我们应该有专门的服务进行日志收集和查询,异常监控方面可以做到指定到人且不同渠道(邮件、电话、钉钉等)的报警。