引子就算是浩哥布置的一个假期小任务吧,你会向别人如何安利Flink?
前前后后学习了半个来月Flink的相关知识,我大概能算是一只脚迈进大门了吧,哈哈~
一、初中班主任反复说,要“透过现象看本质”
Flink,其实就是一个魔法管道,各式各样的原始数据从管道一段流入,经过“24k magic”,从管道另一端流出我们想要的数据精华。
举一个更形象的例子,就像是原油精炼,通过蒸馏,首先摒弃了杂质,其次由于沸点不同,将多种产物进行分层,便于收集。
二、没有对比就没有伤害
Flink作为流式计算的佼佼者,了解它的过程中必然不可避免地要与传统的批量计算进行对比。
批量计算很容易理解,像是从mysql取数据做数据处理就是一种典型的批量计算,它的特点就是在计算之前,我们已经能够完全掌握数据的全貌,因此能够有的放矢,针对性地进行计算处理。
而流式计算的数据是不断汹涌而来的,我们必须它经过管道之时做出及时的处理,否则它就“滚滚东逝”了,因此,我们也无法在完全理解数据的情况下做处理,数据处理的阵地应当是在数据到来之前就布置好的。
既然流式计算看上去没那么美,那么,我们为什么要用它来取代批量计算呢?重点就是一个词,实时性。
想象这样一个场景,公司的业务系统每天会收集到海量数据,作为一名数据民工,你需要及时对数据进行处理,交由业务部门做进一步分析。
传统的批量计算,需要你在固定时间段,后先汇总数据,再做一些跑批处理,此时,业务部门才能看到之前一段时间在市场上,究竟发生了什么。
而流式计算可以实时地将新鲜出炉的数据稍做加工热乎地递给业务部门,可能五秒前甲公司做出的违规操作,警报即刻拉起。
市场大势瞬息万变,对于决策层来说,事件响应速度自然是越快越好,因此,我们就有了使用流式计算的强烈动机。
三、那么,狗蛋,代价是什么呢?
钢之炼金师里有一句经典的台词,等价交换。确实,凡事都是有得便有失,既然流式计算具备如此强大的实时性,那么它也会面对一系列实时处理中不可避免的问题。
网络的不确定性决定了,Flink接收的数据次序与业务逻辑顺序必然不完全一致,比如,我们先收到了一笔订单的结算信息,a few moments later,订单的登记信息才姗姗来迟。
网络的不稳定性决定了,Flink工作时可能会宕机,比如,上游算子已经算到了第五笔订单,而下游算子在算到第二笔订单时扑街,系统恢复之后,中间的几笔交易已经流逝。
网络的转瞬即逝决定了,Flink需要上下文信息时,斯人已不在,比如,统计交易量同时需要账号编码和单笔交易额两类信息,而它们分别储存于登记信息和结算信息,在茫茫数据流中没有交集。
饭要一口一口吃,问题,我们也一个一个地去解决。
四、状态,宝贵的资源
从发展的眼光看,这个世界上没有一座孤岛,数据亦如此。
我们可以将数据流看成是客观事实连续性的具体离散表现,它用一张张快照记录下事件发生过程中的无数个瞬间。
因此,如果我们想要一览数据全貌,就要考虑到数据的上下文信息,把握了前因后果,数据的逻辑性就跃然纸上了。
前文也提到了,流式数据的特殊性决定了,每个算子,只能与每组数据相遇一次,想要充分利用数据,就要及时记录下数据的一些特征,以备不时之需。
在Flink中,我们把这些特征,称为状态。
4.1 状态怎么用?
状态其实就是一类可维护的变量,我们在整个计算生命周期中,主要用到的操作包括初始化、更新、取值、清理等。
为了便于对状态进行分门别类的管理,Flink提供了两类组织方式,Keyed State 和 Operater state,分别以数据键值和算子为单位区分状态。
为了适应不同数据结构的使用需要,Flink提供了多种数据映射方法,ValueState,ListState,ReducingState,FoldingState,MapState等。
4.2 状态如何维护?
运算过程中难免会遇到挂掉的情况,谁也不愿面对从头到来的窘境,因此及时地保存中间状态,以及迅速恢复是十分有必要的。
4.2.1 状态后端
状态后端用于维护状态信息,具体可以分为本地状态管理和远程状态备份两大类。
本地状态管理,以对象的方式可以存储在JVM的堆中,这种方式读写延迟会较低,但是受限于内存大小。对于大容量状态,可以将对象序列化后存储于RocksDB,这样其实是将状态写到了磁盘上。
远程状态备份,MemoryStateBackend将状态存储在内存中,FsStateBackend将状态写入文件系统中,如HDFS,RocksDBStateBackend将状态写入RocksDB。前两者的状态管理都是通过JVM堆,因此会受到内存容量的限制,而第三种方式能实现增量备份。
4.2.2 状态快照
状态的恢复可以基于两类检查点,分别为checkpoint 和savepoint,checkpoint 为Flink在运算执行过程中自动执行的,而savepoint是用户手动创建的。
状态快照的核心是stream barrier,它基于分布式快照算法Chandy-Lamport 改进而来。
barrier可以理解为是穿插在数据流中的flag,它标识了数据流的不同阶段,两个barrier中间包夹的部分就是一个snapshot应当记录的区间。
考虑到Flink的算子可能是分布式组织的,因此算子间存在一个等待机制,称为barrier对齐。
具体而言,算子A可能有多个输入流,当它接收到某条输入流的barrier N标识后,在收到其他剩余输入流的barrier N之前,它需要将输入流中“超前”的数据都先保管在缓存中,以免发生阶段性数据混淆。而在所有输入流都到达barrier N之后,它要在输出流中盖戳barrier N,标志着该阶段快照的完成。之后,它会从缓存中取出之前尚未处理的积压数据,直到遇到下一个barrier N+1。
4.2.3 管理成果
对状态的不同管理方式可以达成三个不同程度的数据一致性状态:
a) exactly once,精确一次,确保每个事件只能精确地一次性影响到Flink的状态管理
b) at leastly once,至少一次,可以保证所有事件不会丢失,但是无法确保是否会冗余处理
c) at mostly once,至多一次,状态不会从快照中恢复,发生事故即丢失
五、时间,永恒的话题
世间没有人没有事能够逃脱时间的注视,Flink也是如此。
在Flink中,可以处理三类不同的时间,分别为event time,ingestion time,processing time,顾名思义,分别对应事件发生的时刻,事件进入Flink的时间,以及事件被Flink处理的时间。一般最常用的还是事件的自然发生时间。
5.1 打开了一扇窗
由于Flink的数据流次序其实是与逻辑时间无关的,因此如果我们想要做一些与时间维度相关的分析工作,就需要用到窗口这个概念。
窗口,在二维的时间流中界定了开始时间和结束时间,自然而然地将连续的事件流划分到了一个个时间段范畴内。
刀法不同,菜品各异。窗口的划分也是如此,通常Flink根据以下几类方法区分时间段:
a) tumbling windows,翻滚窗口,窗口长度和窗口间隔相同,窗口之间无重叠,
b) sliding windows,滑动窗口,窗口长度大于窗口间隔,窗口之间有重叠
c) session windows ,会话窗口,窗口之间不连续,消息间隙大于阈值后自动分离
d) global windows,全局窗口, 通常由用户自定义
5.2 迟到没有什么大不了
由于数据流可能是时间上乱序的,因此我们可能要在处理10秒后的事务和接收5秒前的事务之间反复横跳。
迟到没什么大不了,给出弥补机制就Ok了,但是来得太晚显然还是会打乱全盘计划。为此,Flink设置了一个迟到容忍度指标——Watermarks,来应对姗姗来迟的“邋遢虫”们。
Watermarks的时间量度通常是由用户根据经验定义的,比如广而告之还在路上的数据们,还有30秒北京时间10:30-10:31的窗口即将关闭,过时不候。
一马当先,event time10:31:15的数据到达,算子把指一算,决定暂时视而不见。
紧随其后,event time10:30:45的数据到达,算子把指一算,“huh?you got lucky this time”。
终于,event time10:31:30的数据到达,算子把指一算,好么,可算来了,窗口已关,过时不候。
六、做工具总该有个API吧?
克服重重险阻,Flink将新鲜出炉的强力流计算工具呈现在我们面前,那么我们如何开始享用呢?事实上,Flink提供了三个不同层次的API调用方式。
6.1 SQL/Tabel API
Flink借助Apache Calcite实现了类似SQL查询的查询接口,帮助用户获取流数据信息,有谁不喜欢写两句SQL就能实现功能呢?
6.2 DataStream API & DateSet API
在更细节的层面,我们可以在数据流到来时就开始着手对数据进行加工处理。DateSet对应批处理,我们暂且不表,DataStream中可讲的故事,那就多了。
6.2.1