目录
Flink的特性
- 高吞吐、低延迟、高性能
- 支持带事件时间的窗口(window)操作:time、count、session、data-driven
- 支持有状态计算的exactly once语义
- 支持具有反压功能的持续流模型
- 支持基于轻量级分布式快照(snapshot)实现的容错
- 同时支持batch on streaming处理和Streaming处理
- Flink在JVM内部实现了自己的内存管理
- 支持迭代计算
- 支持程序自动优化:避免特定情况下shuffle、排序等昂贵操作,中间结果有必要时缓存
Flink技术栈
- 同spark一样,Flink也有Flink Core(runtime层)来统一支持流处理和批处理
- Flink Core(runtime层):是一个分布式的流处理引擎,提供了支持Flink计算的全部核心实现
- 支持分布式流处理
- JobGraph到ExecutionGraph的映射、调度,为上层API层提供基础服务
- Flink API层:实现面向Stream的流处理和面向batch的批处理API
- 特定应用领域库:
- Flink ML:提供机器学习Pipelines API并实现多种机器学习算法
- 图计算库Gelly:提供了图计算相关API和多种图计算算法实现
Flink任务调度原理
- Client 为提交 Job 的客户端,可以是运行在任何机器上(与 JobManager 环境连通即可)。提交 Job 后,Client 可以结束进程(Streaming的任务),也可以不结束并等待结果返回。
- JobManager 主要负责调度 Job 并协调 Task 做 checkpoint,职责上很像 Storm 的 Nimbus。从 Client 处接收到 Job 和 JAR 包等资源后,会生成优化后的执行计划,并以 Task 的单元调度到各个 TaskManager 去执行。
- TaskManager 在启动的时候就设置好了槽位数(Slot),每个 slot 能启动一个 Task,Task 为线程。从 JobManager 处接收需要部署的 Task,部署启动后,与自己的上游建立 Netty 连接,接收数据并处理。
- 每一个worker(TaskManager)都是一个JVM进程,它可能会在独立的线程上执行一个或多个subtask。为了控制一个worker能接收多少个task,worker通过task slot来进行控制(一个worker至少有一个task slot)。
- 每个task slot表示TaskManager拥有资源的一个固定大小的子集。假如一个TaskManager有三个slot,那么它会将其管理的内存分成三份给各个slot。资源slot化意味着一个subtask将不需要跟来自其他job的subtask竞争被管理的内存,取而代之的是它将拥有一定数量的内存储备。需要注意的是,这里不会涉及到cpu的隔离,slot目前仅仅用来隔离task的受管理的内存。
- 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执行图
Flink 中的执行图可以分成四层:StreamGraph -> JobGraph -> ExecutionGraph -> 物理执行图。
- StreamGraph:是根据用户通过 Stream API 编写的代码生成的最初的图。用来表示程序的拓扑结构。
- JobGraph:StreamGraph经过优化后生成了 JobGraph,提交给 JobManager 的数据结构。主要的优化为,将多个符合条件的节点 chain 在一起作为一个节点,这样可以减少数据在节点之间流动所需要的序列化/反序列化/传输消耗。
- ExecutionGraph:JobManager 根据 JobGraph 生成ExecutionGraph。ExecutionGraph是JobGraph的并行化版本,是调度层最核心的数据结构。
- 物理执行图:JobManager 根据 ExecutionGraph 对 Job 进行调度后,在各个TaskManager 上部署 Task 后形成的“图”,并不是一个具体的数据结构。
Flink API
- DataSet:对静态数据进行批处理操作、将静态数据抽象成分布式数据集,使用Flink各种操作符处理数据集,支持Java、Scala、Python
- DataStream:对数据流进行流处理操作,将流式的数据抽象成分布式数据流,用Flink各种操作符处理数据流,支持Java、Scala
- Table API:对结构化数据进行查询操作,将结构化数据抽象成关系表。并通过类sql的DSL对关系表进行各种查询操作,支持Java、Scala
- DataStream 面向Stream的流处理。无界
- DataSet面向Batch的批处理。有界+无界
- Table API 面向Stream和Batch的处理。
任务执行
- runtime层以JobGraph形式接受程序。JobGraph即为一个一般化的并行计算数据流图,它拥有任意数量的Task来接收和产生data stream。
- DataStream API 和 DataSet API都会使用单独编译的处理方式生成JobGraph。DataSet API使用optimizer来决定针对程序的优化方法,而DataStream API则使用stream builder来完成该任务
- 在执行JobGraph时,Flink提供了多种候选部署方案(如local,remote,YARN等)
- Flink附随了一产生DataSet或DataStream API程序的类库和API:处理逻辑表查询Table,机器学习的FlinkML,图像处理的Gelly,复杂时间处理的CEP。
时间操作
- 事件时间(Event Time):事件创建时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条日志都会记录自己的生成时间,Flink通过时间戳分配器访问事件时间戳。
- 采集时间(Ingestion Time):事件进入到Flink DataFlow的时间
- 处理时间(Processing Time):某个Operator对事件进行处理的本地系统时间。默认的时间属性就是Processing Time。
在Flink的流式处理中,绝大部分的业务都会使用eventTime,一般只在eventTime无法使用时,才会被迫使用ProcessingTime或者IngestionTime。
window操作
Window可以分成两类:
对于TimeWindow,可以根据窗口实现原理的不同分成三类:滚动窗口(Tumbling Window)、滑动窗口(Sliding Window)和会话窗口(Session Window)。
WindowAPI 都由滚动窗口和滑动窗口。
- TimeWindow是将指定时间范围内的所有数据组成一个window,一次对一个window里面的所有数据进行计算。
- Countwindow根据窗口中相同key元素的数量来触发执行,执行时只计算元素数量达到窗口大小的key对应的结果。
事件窗口EventTimeWindow API:
- TumblingEventTimeWindows 滚动窗口
- SlidingEventTimeWindows 滑动窗口
- EventTimeSessionWindows 会话窗口(Session Window)。
基本操作:
- window:创建自定义窗口
- Trigger:触发器,决定一个窗口何时被计算或清除
- evictor:驱逐者,在Trigger触发后且窗口被处理前,剔除窗口中不需要的元素(filter)
- apply:自定义window function
Flink 中窗口机制和时间类型是完全解耦的,也就是说当需要改变时间类型时不需要更改窗口逻辑相关的代码。
Flink API
越顶层越抽象,表达含义越简明,使用越方便。
越底层越具体,表达能力越丰富,使用越灵活。
Flink架构
- 当系统本地启动时,一个JobManager和一个TaskManagere会启动在同一个JVM中。
- 当一个程序被提交后,系统创建一个Client来进行预处理,将程序转变成一个并行数据流形成,交给JobManager和TaskManager执行。
- Job Managers:负责协调Flink系统调度Task,协调检查点,协调失败时恢复等。
- Task Managers:执行并行程序的worker。
- 每个worker(TaskManager)是一个JVM进程,里面运行一个或多个线程,每个worker能接收多少task,由task Slot来控制。
- 一个TaskManager进行中有多个subtask线程,意味着task将共享TCP连接(基于多路复用)和心跳消息,共享数据集和数据结构,减少每个task的负担。
这里的slot只对内存隔离管理,cpu不进行隔离。
Flink反压机制
- Storm: 通过监控process bolt中接收队列负载情况来处理反压,即当超过高水位值,就将反压信息写到Zookeeper,由zookeeper的watch通知worker进入反压状态,最后spout停止发送tuple。
- Spark Streaming:设置属性“spark.streaming.backpressure.enabled”进行自动反压,即动态控制数据接收速率来适配集群数据处理能力。
- Flink:不需要设置,自动处理反压,即每个组件都有对应的分布式阻塞队列,只有队列不满的情况,上游才发数据,较慢的接收者会自动降低发送速率,如果队列满了(有界队列),发送者会阻塞。
Flink与Spark Streaming
- 数据模型:
- spark 采用 RDD 模型,spark streaming 的 DStream 实际上也就是一组 组小批数据 RDD 的集合
- flink 基本数据模型是数据流,以及事件(Event)序列
- 运行时架构:
Flink+Kafka实现exactly-once语义
- Flink通过checkpoint来保存数据是否处理完成的状态。
- 由JobManager协调各个TaskManager进行checkpoint存储,checkpoint保存在 StateBackend中,默认StateBackend是内存级的,也可以改为文件级的进行持久化保存。
- 执行过程实际上是一个两段式提交,每个算子执行完成,会进行“预提交”,直到执行完sink操作,会发起“确认提交”,如果执行失败,预提交会放弃掉。
- 如果宕机需要通过StateBackend进行恢复,只能恢复所有确认提交的操作。