事件的流转
- 一个程序由source,Transformation,sink组成
- 每个Transformation可以设置多个分区,如下图可以看到 source到map是one-to-one streams。
- 处理上述,还有另一种是redistributing streams,代表的算子有keyby(),broadcast,rebalance,这种方式数据会从一个上游流转到多个下游。
Window
- Window表示一个窗体,在Flink中,数据是一条条处理的,如果想批量处理,可以将其划分到Window中,一批批处理。
- 常见的Window有:
- Count Window
- Time Window (Tumbling Window、Sliding Window、Session Window)
- 自定义Window
Time
上面的窗口涉及到以个数作为窗口的,也有以时间为窗口的,在Flink中,时间有3种:
- Event Time:事件产生的时间,如果指定该时间必须指定watermark
- Ingestion Time:Flink 摄取事件时间
- Processing Time:Flink算子处理的时间(默认)
如果想改变Flink程序的时间,可以使用 env.setStreamTimeCharacteristic()
Watermarks
实时分布式系统中,由于网络问题造成某些数据发到flink时间延时与事件产生的时间,对于迟早的元素,window不可能无限等到,watermarks就是触发window计算的一种机制。
在某些情况下,基于EventTime的数据流是有序的,在有序流里,WM是一种简单的周期性标记。
更多情况下,基于EventTime的数据是无序,比如Kafka是分区有序全局无序,数据从产生有序到Kafka消费无序。在无序流中,watermarks告诉operator更早的事件已经到了,可以触发windows计算了。如下是某个算子接收的事件流。
从中可以看出wm不是均匀发布的,W(3)表示抽象时间戳3,7表示事件时间戳,当W(3)到达时,它告诉operator可以计算3以前的window计算了。在乱序的情况下,事件时间19明显延迟了,会被抛弃掉。
Watermarks的产生和传递规则
- 产生:通常情况下,wm在source函数中产生,但也可以在source后任何阶段产生,但是在windows之前必须指定。
- one-to-one传递:每个subtask都有独立的wm,wm会通过operator推进当前eventtime,同时op会为下游生成一个新的wm.
- redistribute-stream:当前event time由其输入的多个eventtime最小值决定。
WaterMarks的代码实现
只有基于EventTime的流处理程序需要指定Timestamp和watermarks.分配timestamp&watermarks有两种方式:
- 直接在sourceFunction(SourceFunction / RichParallelSourceFunction)中实现。
- 在算子后面调用,但是必须在window前被调用。
上面提到传入Watermark,这里我们讲下三种类型的watermark:
- Periodic Watermarks:最常用的方式,基本Timer,需要实现AssignerWithPeriodicWatermarks接口ExecutionConfig.setAutoWatermarkInterval(msec)(指定发送周期,默认200ms)
- Pucuated WaterMarks,基于某些事件触发watermark 的生成和发送,发送的规则逻辑由业务逻辑自定义,实现AssignerWithPeriodicWatermarks接口
- 适用于event时间戳单调递增的场景的WaterMarks集成类:
- 适用于预先知道最大延迟的场景(例如最多比之前的元素延迟3000ms)的WaterMarks集成类
有状态的操作
- 什么是状态?就是能保存数据的算子,以前处理事件的算子如果想保存数据必须用第三方如redis,现在算子里面也可以存数据了。
- 状态分为2种:Operator State 和 Keyed State
- Operator State 表示一个并发的Operator 绑定一个状态
- Keyed State 表示状态与Key进行绑定。
- 上面2种状态可以以原始状态和托管状态的形式存在。
- State Backend (rocks db + hdfs),这个后面再解释。
Checkpoint
- 什么是Checkpoint?在某一时刻,将所有task的状态做一个snapshot,然后存储到 State Backend 中。使用Checkpoint 可以做到如下:
- 轻量级容错机制
- 保证exactly-once语义
- 用于内部的失败恢复(自动)
- 基本原理:1.通过往source注入barrier 2.barrier 作为Checkpoint的标志。
Savepoint
- savepoint 可以理解为特殊的checkpoint,需要手动触发,而且不会过期,不会被覆盖,除非手动删除。一般除了对集群做重大改动需要停止任务,否则不需要设置savepoint。
- 保存流式处理的历史版本,其具有replay的功能
- 用于外部的恢复(应用重启和升级)
- 两种触发方式:A Cancel With savepoint B.手动主动触发。
Flink的运行时架构
Flink 中的执行图可以分成四层:
- StreamGraph:根据用户编写的代码生成的最初的图,用来表示程序拓扑结构。
- JobGraph:StreamGraph经过优化后生成了 JobGraph,主要的优化是将多个one-to-one 节点 chain 在一起作为一个节点,减少数据在节点之间流动。
- ExecutionGraph:在 JobManager 中生成的,产生并行拓扑。
- 物理执行图:在各个TaskManager 上部署 Task 后形成的“图”,并不是一个具体的数据结构。
- 当 Flink集群启动后,首先会启动一个 JobManger和一个或多个的 TaskManager。
- Client提交前生成StreamGraph,再优化成JobGraph后将任务交给 JobManager,客户端断开连接。
- JobManager 从 Client处接收到 Job和 JAR包等资源后,会生成优化后的ExecutionGraph,并以 Task 的单元调度到各个 TaskManager去执行。
- 在TaskManager 中生成物理执行图并执行,然后 TaskManager 将心跳和统计信息汇报给 JobManager。TaskManager之间以流的形式进行数据的传输。
任务在TaskManager运行
上面提到我们的任务最后会以Task的方式在TaskSlot中运行,接下来讲讲:
- TaskManager是进程,Task是线程(内部运行operator任务),SubTask表示一个task并发的实例。
- 一个task / SubTask 线程只能放在一个Task Slot中。
- 如果并发数一样,又是one-to-one的流,那么可以将operator串在一起作为operatorchain,放在一个Task 中减少线程数据交换。
- slot 均分 taskmanager 所托管的内存。一个taskmanager有n个 slot,如果有很多task需要处理,可以通过共享slot来实现。
operatorchain的组成条件
- 没有禁用Chain
- 上下游并发度一致,下游算组的入度为1
- 上下游算子在同一个slot group
- 下游节点 chain 策略为 ALWAYS (可以与上下游链接,map,flatmap,filter等默认是ALWAYS )
- 上游节点 chain 策略为 ALWAYS和HEAD (只能与下游链接,不能与上游链接,source默认是HEAD )
- 上下游算子之间没有shuffle (数据分区方式是 forward)
- 可以通过代码的方式改变 OperatorChain。
共享slot
默认情况下,Flink允许来自同一Job的不同subtask共享slot,结果可能会导致一个 slot 持有该 Job的整个pipleline。这样每个taskmanager执行的时间就差不多了,并且 flink job 的最高并发度最好不要超过slot的个数。
如上图,有2个group,一个并发度为10,需要10个slot,另一个最高并发度为20,需要20个slot,所以整个任务需要30个slot。