一. 应用
二. 抽象
三. 时间与窗口
四. 类型与序列化
五. 内存管理
六. 状态管理
七. 作业提交
八. 资源管理
九. 作业调度
十. 作业执行
十一. 数据交换
十二. 应用容错
十三. SQL
十四. 运维监控
应用
一. Flink应用开发
- 获取参数
- 初始化stream执行环境
- 配置参数
- 读取外部数据
- 数据处理
- 将处理结果写入外部
- 触发执行
二. API层次
三. 数据流
- DataStream
- DataStreamSource
- DataStreamSink
- KeyedStream
- WindowedStream & AllWindowedStream
- JoinedStreams & CoGroupedStreams:Join是CoGroup的一种特例,JoinedStreams底层使用CoGroupedStreams来实现。两者的区别如下。CoGrouped侧重的是Group,对数据进行分组,是对同一个key上的两组集合进行操作
- ConnectedStreams
- BroadcastStream & BroadcastConnectedStream
- IterativeStream:IterativeDataStream是对一个DataStream的迭代操作,从逻辑上来说,包含IterativeStream的Dataflow是一个有向有环图,在底层执行层面上,Flink对其进行了特殊处理。
- AsyncDataStream:AsyncDataStream是个工具,提供在DataStream上使用异步函数的能力
四. 数据流API
- 数据读取
从内存中读取数据(直接在内存中生成数据,方便测试和演示),文件读取数据(文件读取的模式有一次性读取FileProcessingMode.PROCESS_ONCE和持续读取FileProcessingMode.PROCESS_CONTINUOUSLY。),Socket接入数据,自定义读取(自定义数据读取就是使用Flink连接器、自定义数据读取函数,与外部存储交互,读取数据,如从Kafka、JDBC、HDFS等读取) -
处理数据
- 数据写入
-
旁路输出
只有在特定的函数中才能使用旁路输出,具体如下
1)ProcessFunction。
2)KeyedProcessFunction。
3)CoProcessFunction。
4)ProcessWindowFunction。
5)ProcessAllWindowFunction。
6)ProcessJoinFunction。
7)KeyedCoProcessFunction。
核心抽象
一. 执行环境
- LocalStreamEnvironment:本地执行环境,在单个JVM中使用多线程模拟Flink集群
- RemoteStreamEnvironment:在大规模数据中心中部署的Flink生成集群的执行环境
- StreamContextEnvironment:在Cli命令行或者单元测试时候会被使用
- StreamPlanEnvironment:在Flink Web UI管理界面中可视化展现Job的时候,专门用来生成执行计划(实际上就是StreamGraph)
- ScalaShellStreamEnvironment:这是Scala Shell执行环境,可以在命令行中交互式开发Flink作业
二. 运行时环境
- RuntimeEnvironment:在Task开始执行时进行初始化,把Task运行相关的信息都封装到该对象中,其中不光包含了配置信息,运行时的各种服务也会被包装到其中
.....
public class RuntimeEnvironment implements Environment {
private final JobID jobId;
private final JobVertexID jobVertexId;
private final ExecutionAttemptID executionId;
private final TaskInfo taskInfo;
private final Configuration jobConfiguration;
private final Configuration taskConfiguration;
private final ExecutionConfig executionConfig;
private final UserCodeClassLoader userCodeClassLoader;
private final MemoryManager memManager;
private final IOManager ioManager;
private final BroadcastVariableManager bcVarManager;
private final TaskStateManager taskStateManager;
private final GlobalAggregateManager aggregateManager;
private final InputSplitProvider splitProvider;
private final ExternalResourceInfoProvider externalResourceInfoProvider;
private final Map> distCacheEntries;
private final ResultPartitionWriter[] writers;
private final IndexedInputGate[] inputGates;
private final TaskEventDispatcher taskEventDispatcher;
private final CheckpointResponder checkpointResponder;
private final TaskOperatorEventGateway operatorEventGateway;
private final AccumulatorRegistry accumulatorRegistry;
private final TaskKvStateRegistry kvStateRegistry;
private final TaskManagerRuntimeInfo taskManagerInfo;
private final TaskMetricGroup metrics;
private final Task containingTask;
- SavepointEnvironment
三. 运行时上下文
RuntimeContext是Function运行时的上下文,封装了Function运行时可能需要的所有信息,让Function在运行时能够获取到作业级别的信息,如并行度相关信息、Task名称、执行配置信息(ExecutionConfig)、State等
四. 数据流元素
- StreamRecord
StreamRecord包含数据的值本身,事件戳(可选)也叫作数据记录。 - LatencyMarker
LatencyMarker用来近似评估延迟,LatencyMarker在Source中创建,并向下游发送,绕过业务处理逻辑,在Sink节点中使用LatencyMarker估计数据在整个DAG图中流转花费的时间,用来近似地评估总体上的处理延迟。LatencyMarker包含周期性地在数据源算子中创造出来的时间戳,算子编号,数据源算子所在的Task编号 - Watermark
Watermark是一个时间戳,用来告诉算子所有时间早于等于Watermark的事件或记录都已经到达,不会再有比Watermark更早的记录,算子可以根据Watermark触发窗口的计算、清理资源等 - StreamStatus
用来通知Task是否会继续接收到上游的记录或者Watermark。StreamStatus在数据源算子中生成,向下游沿着Dataflow传播,StreamStatus可以表示两种状态:空闲状态(IDLE),活动状态(ACTIVE)
五. 数据转换
-
物理Transformation
物理Transformation一共有4种,具体如下 SourceTransformation,SinkTransformation,OneInputTransformation,TwoInputTransformation
- 虚拟Transformation
SideOutputTransformation(SideOutputTransformation在旁路输出中转换而来,表示上游Transformation的一个分流,上游Transformation可以有多个下游SideOutputTransformation),SplitTransformation,SelectTransformation,PartitionTransformation,UnionTransformation(Union运算要求其直接上游输入的数据的结构必须是完全相同的),FeedbackTransformation,CoFeedbackTransformation
六. 算子
所有的算子都包含了生命周期管理、状态与容错管理、数据处理3个方面的关键行为。
生命周期管理
1)setup:初始化环境、时间服务、注册监控等。
2)open:该行为由各个具体的算子负责实现,包含了算子的初始化逻辑,如状态初始化等。算子执行该方法之后,才会执行Function进行数据的处理。
3)close:所有的数据处理完毕之后关闭算子,此时需要确保将所有的缓存数据向下游发送。
4)dispose:该方法在算子生命周期的最后阶段执行,此时算子已经关闭,停止处理数据,进行资源的释放。
状态与容错管理
算子负责状态管理,提供状态存储,触发检查点的时候,保存状态快照,并且将快照异步保存到外部的分布式存储。当作业失败的时候算子负责从保存的快照中恢复状态
数据处理
算子对数据的处理,不仅会进行数据记录的处理,同时也会提供对Watermark和LatencyMarker的处理。算子按照单流输入和双流输入,定义了不同的行为接口
七. 函数体系
-
函数层次
UDF在DataStream API层使用,Flink提供的函数体系从接口的层级来看,从高阶Function到低阶Function
RichFunction相比无状态Function,有两方面的增强:
1)增加了open和close方法来管理Function的生命周期,在作业启动时,Function在open方法中执行初始化,在Function停止时,在close方法中执行清理,释放占用的资源等。无状态Function不具备此能力。
2)增加了getRuntimeContext和setRuntimeContext。通过RuntimeContext,RichFunction能够获取到执行时作业级别的参数信息,而无状态Function不具备此能力。
无状态Function天然是容错的,作业失败之后,重新执行即可,但是有状态的Function(RichFunction)需要处理中间结果的保存和恢复,待有了状态的访问能力,也就意味着Function是可以容错的,执行过程中,状态会进行快照然后备份,在作业失败,Function能够从快照中恢复回来。 -
处理函数
ProcessFunction:单流输入函数。CoProcessFunction:双流输入函数。KeyedProcessFunction:单流输入函数。KeyedCoProcessFunction:双流输入函数。
-
广播函数
异步函数
-
数据源函数
输出函数
检查点函数
八. 数据分区
- 自定义分区
- ForwardPartitioner
- ShufflePartitioner
- ReblancePartitioner
- RescalingPartitioner
- BroadcastPartitioner
- KeyGroupStreamPartitioner
九. 连接器
连接器在Flink中叫作Connector。Flink本身是计算引擎,并不提供数据存储能力,所以需要访问外部数据,外部数据源类型繁多,连接器因此应运而生,它提供了从数据源读取数据和写入数据的能力。基于SourceFunction和SinkFunction构建出了种类繁多的连接器
十. 分布式ID
在分布式计算中,Flink对所有需要进行唯一标识的组件、对象提供了抽象类AbstractID,因为需要跨网络进行传递,所以该类实现了Serializable接口,需要比较唯一标识是否相同,所以也实现了Comparable接口
时间与窗口
如果想了解流处理系统中如何实现强一致性,可以参考MillWheel:Fault-Tolerant Stream Processing at Internet Scale和Discretized Streams:Fault-Tolerant Streaming Computation at Scale两篇论文
一. 时间类型
- 事件时间:事件时间指事件发生时的时间,一旦确定之后再也不会改变。例如,事件被记录在日志文件中,日志中记录的时间戳就是事件时间。通过事件时间能够还原出来事件发生的顺序。使用事件时间的好处是不依赖操作系统的时钟,无论执行多少次,可以保证计算结果是一样的,但计算逻辑稍微复杂,需要从每一条记录中提取时间戳
- 处理时间处理时间指消息被计算引擎处理的时间,以各个计算节点的本地时间为准,使用处理时间依赖于操作系统的时钟,重复执行基于窗口的统计作业,结果可能是不同的。处理时间的计算逻辑非常简单,性能好于事件时间,延迟低于事件时间,只需要获取当前系统的时间戳即可。
- 摄取时间摄取时间指事件进入流处理系统的时间,对于与一个事件来说,使用其被读取的那一刻的时间戳作作为摄取时间。
二. 窗口类型
- Count Window
Tumble Count Window:累积固定个数的元素就视为一个窗口,该类型的窗口无法像时间窗口一样事先切分好。
Sliding Count Window:累积固定个数的元素视为一个窗口,每超过一定个数的原则个数,则产生一个新的窗口。 - Time Window
Tumble Time Window:表示在时间上按照事先约定的窗口大小切分的窗口,窗口之间不会相互重叠。
Sliding Time Window:表示在时间上按照事先约定的窗口大小、滑动步长切分的窗口,滑动窗口之间可能会存在相互重叠的情况。 - Session Window
Session Window是一种特殊的窗口,当超过一段时间,该窗口没有收到新的数据元素,则视为该窗口结束,所以无法事先确定窗口的长度、元数个数,窗口之间也不会相互重叠。
三. 窗口原理与机制
-
WindowAssigner
WindowTrigger
Trigger触发器决定了一个窗口何时能够被计算或清除,每一个窗口都拥有一个属于自己的Trigger,Trigger上会有定时器,用来决定一个窗口何时能够被计算或清除。每当有元素加入该窗口,或者之前注册的定时器超时时,Trigger都会被调用。Trigger触发的结果如下。
1)Continue:继续,不做任何操作。
2)Fire:触发计算,处理窗口数据。
3)Purge:触发清理,移除窗口和窗口中的数据。
4)Fire + Purge:触发计算+清理,处理数据并移除窗口和窗口中的数据。WindowEvictor
1)CountEvictor: 计数过滤器。在Window中保留指定数量的元素,并从窗口头部开始丢弃其余元素。
2)DeltaEvictor: 阈值过滤器。本质上来说就是一个自定义规则,计算窗口中每个数据记录,然后与一个事先定义好的阈值做比较,丢弃超过阈值的数据记录。
3)TimeEvictor: 时间过滤器。保留Window中最近一段时间内的元素,并丢弃其余元素。Window函数
1)增量计算函数
2)全量计算函数
四. 水印
- Watermark的生成机制
(1)周期性Watermark策略周期性Watermark策略在Flink中叫作PeriodicWatermarkAssigner,周期性(一定时间间隔或者达到一定的记录条数)地产生一个Watermark。
1) AscendingTimestamps:递增Watermark,作用在Flink SQL中的Rowtime属性上,Watermark=当前收到的数据元素的最大时间戳-1,此处减1的目的是确保有最大时间戳的事件不会被当做迟到数据丢弃。
2)BoundedOutOfOrderTimestamps:固定延迟Watermark,作用在Flink SQL的Rowtime属性上,Watermark=当前收到的数据元素的最大时间戳-固定延迟。
(2)每事件Watermark策略每事件Watermark策略在Flink中叫作PuntuatedWatamarkAssigner,数据流中每一个递增的EventTime都会产生一个Watermark。在实际的生产中Punctuated方式在TPS很高的场景下会产生大量的Watermark,在一定程度上会对下游算子造成压力
(3)无为策略无为策略在Flink中叫作PreserveWatermark。在Flink中可以使用DataStream API和Table & SQL混合编程,所以Flink SQL中不设定Watermark策略,使用底层DataStream中的Watermark策略
- 多流的Watermark
Flink内部实现每一个边上只能有一个递增的Watermark,当出现多流携带EventTime汇聚到一起(GroupBy或Union)时,Apache Flink会选择所有流入的EventTime中最小的一个向下游流出,从而保证Watermark的单调递增和数据的完整性
五. 时间服务
- 定时器服务
定时器服务在Flink中叫作TimerService,窗口算子(WindowOperator)中使用了InternalTimerService来管理定时器(Timer),其初始化是在WindowOperator#open()中实现的 - 定时器
- 优先级队列
六. 窗口实现
- 时间窗口
- 会话窗口
在Flink中提供了4种Session Window的默认实现
1)ProcessingTimeSessionWindows:处理时间会话窗口,使用固定会话间隔时长。
2)DynamicProcessingTimeSessionWindows:处理时间会话窗口,使用自定义会话间隔时长。
3)EventTimeSessionWindows:事件时间会话窗口,使用固定会话间隔时长。
4)DynamicEventTimeSessionWindows:事件时间会话窗口,使用自定义会话间隔时长。 - 计数窗口
类型与序列化
Flink内部自主进行内存管理,将数据以二进制结构保存在内存中,目前的实现中大量使用了堆外内存。如果让开发人员直接操作二进制结构,代码会变得复杂臃肿,所以大数据平台在设计API的时候,允许用户直接像编写普通Java应用程序一样使用其API开发Function,直接使用JDK提供的类型和自定义类型。
一. 物理类型
二. 逻辑类型
三. 类型推断
四. 显式类型
五. Flink Row
六. Blink Row
七. ColumnarRow
内存管理
一. 自主内存管理
- JVM内存管理的不足
垃圾回收
有效数据密度低
OOM问题影响稳定性
缓存未命中问题 - 自主内存管理
因为JVM存在诸多问题,所以越来越多的大数据计算引擎选择自行管理JVM内存,如Spark、Flink、HBase,尽量达到C/C++ 一样的性能,同时避免OOM的发生。 - 堆外内存的不足之处
二. 内存模型
-
内存布局
TaskManager是Flink中执行计算的核心组件,是用来运行用户代码的Java进程。其中大量使用了堆外内存。
- 内存计算
三. 内存数据结构
四. 内存管理器
五. 网络缓冲器
状态管理
一. 状态类型
(1)ValueState
(2)ListState
(3)ReducingState
(4)AggregatingState
(5)MapState
二. 原始和托管状态
按照由Flink管理还是用户自行管理,状态可以分为原始状态(Raw State)和托管状态(Managed State)。原始状态,即用户自定义的State,Flink在做快照的时候,把整个State当作一个整体,需要开发者自己管理,使用byte数组来读写状态内容。托管状态是由Flink框架管理的State,如ValueState、ListState、MapState等,其序列化与反序列化由Flink框架提供支持,无须用户感知、干预。KeyedState和OperatorState可以是原始状态,也可以是托管状态。通常在DataStream上的状态推荐使用托管状态,一般情况下,在实现自定义算子时,才会使用到原始状态。
三. 状态描述
四. 广播状态
在图可以看到,业务数据流是一个普通数据流,规则数据流是广播数据流,这样就可以满足实时性、规则更新的要求。规则算子将规则缓存在本地内存中,在业务数据流记录到来时,能够使用规则处理数据。
五. 状态接口
六. 状态存储
七. 状态持久化
八. 状态重分布
- OperatorState重分布
- KeyedState重分布
九. 状态过期
- DataStream中状态过期
- Flink SQL中状态过期
Flink SQL是数据分析的高层抽象,在SQL的世界里并无State的概念,而在流Join、聚合类的场景中,使用了State,如果State不定时清理,则可能会导致State过多,内存溢出,为了稳妥起见,最好为每个Flink SQL作业提供State清理的策略。如果定时清理State,则存在可能因为State被清理而导致计算结果不完全准确的风险,Flink的Table API和SQL接口中提供了参数设置选项,能够让使用者在精确和资源消耗做折中 - 状态过期清理
默认情况下,只有在明确读出过期值时才会删除过期值,如通过调用ValueState#value()
作业提交
一. 提交流程
二. Graph总览
三. 流图
四. 作业图
五. 执行图
资源管理
一. 资源抽象
二. 资源管理器
资源管理器在Flink中叫作ResourceManager。Flink同时支持不同的资源集群类型,ResourceManager位于Flink和资源管理集群(Yarn、K8s等)之间,是Flink集群级资源管理的抽象,其主要作用如下
1)申请容器启动新的TM,或者为作业申请Slot。
2)处理JobManager和TaskManager的异常退出。
3)缓存TaskManager(即容器),等待一段时间之后再释放掉不用的容器,避免资源反复地申请释放。
4)JobManager和TaskManager的心跳感知,对JobManager和TaskManger的退出进行对应的处理
三. Slot管理器
Slot管理器在Flink中叫作SlotManager,是ResourceManager的组件,从全局角度维护当前有多少TaskManager、每个TaskManager有多少空闲的Slot和Slot等资源的使用情况。当Flink作业调度执行时,根据Slot分配策略为Task分配执行的位置
1)对TaskManager提供注册、取消注册、空闲退出等管理动作,注册则集群可用的Slot变多,取消注册、空闲推出则释放资源,还给资源管理集群。
2)对Flink作业,接收Slot的请求和释放、资源汇报等。当资源不足的时候,SlotManger将资源请求暂存在等待队列中,SlotManager通知ResourceManager去申请更多的资源,启动新的TaskManager,TaskManager注册到SlotManager之后,SlotManager就有可用的新资源了,从等待队列中依次分配资源。
四. SlotProvider
SlotProvider接口定义了Slot的请求行为,支持两种请求模式。
1)立即响应模式:Slot请求会立即执行。
2)排队模式:排队等待可用Slot,当资源可用时分配资源
五. Slot选择策略
六. Slot资源池
Slot资源池在Flink中叫作SlotPool,是JobMaster中记录当前作业从TaskManager获取的Slot的集合。
七. Slot共享
作业调度
一. 调度
二. 执行模式
- 流水线模式即Pipelined,此模式以流水线方式(包括Shuffle和广播数据)执行作业,但流水线可能会出现死锁的数据交换除外。如果可能会出现数据交换死锁,则数据交换以Batch方式执行。当数据流被多个下游分支消费处理,处理后的结果再进行Join时,如果以Pipelined模式运行,则可能出现数据交换死锁
- 强制流水线模式即Pipelined_Forced,此模式以流水线方式(包括shuffle和broadcast数据)执行作业,即便流水线可能会出现死锁的数据交换时仍然执行。一般情况下,Pipelined模式是优先选择,确保不会出现数据死锁的情况下才会使用Pipelined_Forced模式
- 流水线优先模式即Pipelined_With_Batch_Fallback,此模式首先使用Pipelined启动作业,如果可能死锁则使用Pipelined_Forced启动作业。当作业异常退出时,则使用Batch模式重新执行作业
- 批处理模式即Batch,此模式对于所有的shuffle和broadcast都使用Batch模式执行,仅本地的数据交换使用Pipelined模式。
- 强制批处理模式即Batch_Forced,此模式对于所有的数据交换都使用Batch模式,对于本地交换也不例外
三. 数据交换
- BLOCKINGBLOCKING类型的数据分区会等待数据完全处理完毕,然后才会交给下游进行处理,在上游处理完毕之前,不会与下游进行数据交换。该类型的数据分区可以被多次消费,也可以并发消费。被消费完毕之后不会自动释放,而是等待调度器来判断该数据分区无人再消费之后,由调度器发出销毁指令。该模式适用于批处理,不提供反压流控能力。
- BLOCKING_PERSISTENTBLOCKING_PERSISTENT类型的数据分区类似于BLOCKING,但是其生命周期由用户指定。调用JobManager或者ResourceManager API进行销毁,而不是由调度器控制。
- PIPELINEDPIPELINED(流水线)式数据交换适用于流计算和批处理。数据处理结果只能被1个消费者(下游的算子)消费1次,当数据被消费之后即自动销毁。PIPELINED分区可能会保存一定数据的数据,与PIPELINED_BOUNDED相反。此结果分区类型可以在运行中保留任意数量的数据。当数据量太大内存无法容纳时,可以写入磁盘中。
- PIPELINED_BOUNDEDPIPELINED_BOUNDED是PIPELINED带有一个有限大小的本地缓冲池。对于流计算作业来说,固定大小的缓冲池可以避免缓冲太多的数据和检查点延迟太久。不同于限制整体网络缓冲池的大小,该模式下允许根据分区的总数弹性地选择网络缓冲池的大小。对于批处理作业来说,最好使用无限制的PIPELINED数据交换模式,因为在批处理模式下没有CheckpointBarrier,其实现Exactly-Once与流计算不同。
四. 作业生命周期
五. 关键组件
六. 作业启动
七. 作业停止
八. 作业失败调度
九. 组件容错
作业执行
一. 作业执行图
二. 核心对象
三. task执行
数据交换
一. 数据传递模式
二. 关键组件
三. 数据传递
四. 数据传递过程
五. 网络通信
应用容错
一. 容错保证语义
二. 检查点和保存点
- 检查点在Flink中叫作Checkpoint,是Flink实现应用容错的核心机制,根据配置周期性通知Stream中各个算子的状态来生成检查点快照,从而将这些状态数据定期持久化存储下来,Flink程序一旦意外崩溃,重新运行程序时可以有选择地从这些快照进行恢复,将应用恢复到最后一次快照的状态,从此刻开始重新执行,避免数据的丢失、重复。
- 保存点在Flink中叫作Savepoint,是基于Flink检查点机制的应用完整快照备份机制,用来保存状态,可以在另一个集群或者另一个时间点,从保存的状态中将作业恢复回来,适用于应用升级、集群迁移、Flink集群版本更新、A/B测试以及假定场景、暂停和重启、归档等场景。
三. 作业恢复
四. 关键组件
五. 轻量级异步分布式快照
六. 检查点执行过程
七. 检查点恢复过程
八. 端对端严格一次
-
预提交阶段
这种方式只能在数据源Kafka到Flink内部保证严格一次,一旦涉及从Sink写入到外部Kafka就会出现问题了。假设Checkpoint 3完成之后,Source从Topic偏移量位置65536读取了1000条数据,Topic偏移量为66536,Sink写入了1000条数据到外部Kafka,此时Flink应用的1个Sink并行实例因为未处理的异常崩溃,进入Failover阶段,应用自动从Checkpoint 3恢复,重新从Topic的偏移量65536开始读取数据,这就会导致65536~66536之间的1000条数据被重复处理,写入到了Kafka中。这种情况下需要避免重复写入这1000条数据到Kafka中。幂等性是一种解决方案,如对HBase按照主键插入可能有效,第2次插入是对第1次的更新。
-
提交阶段
在预提交阶段,数据实际上已经写入外部存储,但是因为事务的原因是不可读的,所以Sink在事务提交阶段的工作稍微简单一点,当所有的Sink实例提交成功之后,一旦预提交完成,必须确保提交外部事务也要成功,此时算子和外部系统协同来保证。倘若提交外部事务失败(如网络故障等),Flink应用就会崩溃,然后根据用户重启策略进行回滚,回滚到预提交时的状态,之后再次重试提交。
SQL
一. Calcite
二. 动态表
三. TableEnvironment
四. Table API
五. SQL API
六. 元数据
七. 数据访问
八. SQL函数
九. Planner关键抽象
运维监控
一. 监控指标
二. 指标组
三. 监控集成
四. 指标注册中心
五. 指标查询服务
六. 延迟跟踪实现原理
疑惑和难点
会话窗口不同于事件窗口,它的切分依赖于事件的行为,而不是时间序列,所以在很多情况下会因为事件乱序使得原本相互独立的窗口因为新事件的到来导致窗口重叠,而必须要进行窗口的合并???(过程)