前段时间详细地阅读了 《Apache Flink的流处理》 这本书,作者是 Fabian Hueske&Vasiliki Kalavri,国内崔星灿翻译的,这本书非常详细、全面得介绍了Flink流处理,并且以气象数据的例子讲解其中的使用,我把其中一些比较重要的句子做了比较,并且分享给大家。有一些我不是很理解,需要以后慢慢去消化,我就不做详细的展开。
一、传统的数据处理框架
1.1事务型处理
企业在日常业务运营过程中会用到各类基于web的应用,通常是业务系统,比如订单、客户系统等等
通常一个应用对于1个或多个数据库,应用通过执行远程数据库系统的事务来读取或更新状态
1.2分析型处理
存储于不同事务类型数据系统中的数据,可以为企业提供业务运营相关的分析见解,通常是将数据从业务系统的数据库中复制到数仓,然后再进行分析和查询。这个过程称为ETL。
二、Flink和Spark的区别
2.1共同点
高吞吐、在压力下保持正确
2.2不同点:
1.本质上,Spark是微批处理,而Flink是流处理
2.Flink低延迟
3.Flink支持时间语义,可通过WaterMark来处理乱序数据,如果Spark要处理乱序数据只能通过RDD排序来实现
4.Flink支持状态编程,使用方式更加灵活
5.Flink提供精确一次的状态一致性保障
2.3本质区别:
本质上是流与微批的区别
2.4 数据模型:
Spark采用RDD模型,Spark Streaming的DStream实际上也就是一组小批数据的RDD的集合
Flink基本数据是流,以及事件Event序列
2.5运行架构:
Spark是批计算,将DAG划分成不同的stage,一个完成后才可以计算下一个
Flink是标准的流执行模式,一个事件在处理后可以直接发往下一个节点
三、Flink流处理基础
3.1DataFlow图
描述了数据在不同操作之间流动。
通常表现为有向图,顶点表现为算子,表示计算,边表示数据的依赖关系
3.2StreamGraph
根据用户通过StreamAPI编写的代码生成的最初的图,由2部分构成:
1.StreamNode,代表算子,表示计算
2.StreamEdge:连接两个StreamNode的边,表示数据的依赖关系
3.3JobGraph
StreamGraph经过优化后生成了JobGraph,提交给JobManager的数据结构,由以下3个构成:
1.JobVertex:经过优化后符合条件的多个StreamNode可能串联在一起生成1个JobVertex
2.JobEdge:连接JobVertex,代表了JobGraph的依赖关系。
3.IntermediateDataSet:经过JobVertex节点处理的数据输出
3.4ExecutionGraph
JobGraph的并行版本,由JobManager生成,调度底层的核心数据结构
3.5物理执行图
JobManager根据ExecutionGraph对Job进行调度,在TaskManager上部署后形成的图,并不是一个数据结构
四、算子状态
4.1本地变量
单个算子同一并行度子任务可以访问,其余都不行
4.2算子状态(Operator State)
算子状态的作用范围限定为算子任务
由同一个算子同一并行的子任务所处理的所有数据都可以访问到相同的状态
状态对于同一子任务而言是共享的
算子状态不能由相同或不同算子的另一个子任务访问
主要有3种:
ListState:将状态表示为一组数据的列表
Union List State:也是ListState,区别在从savepoint或者checkpoint启动时如何恢复
BroadCast State:广播状态
4.3键控状态(Keyed State)
键控状态是根据输入数据流中定义的键(key)来维护和访问的
key相同的数据所能访问的状态
KeyedState只能在键控流中使用
主要有4种:
ValueState:将状态表示为单个的值
ListState:将状态表示为一组数据的列表
MapState:将状态表示为一组 Key-Value 对
ReducingState:将状态表示为一个用于聚合操作的列表
4.3状态后端:把算子状态写到存储系统
为了保证状态的快速访问,每个并行的任务都会把状态存储在本地JVM,状态后端负责将任务状态以检查点的形式写入到远程持久化存储,该存储可能是一个分布式的文件系统,也可能是某个数据库系统
五.算子扩缩容
算子主要有OperateState和KeyedState,他们的扩缩容方法不一样
5.1键控状态(KeyedState)
KeyState发生重新分配后,key值分配到哪个Task上,则对应的KeyState也会分配到对应的Task上
做法是:将Key值读取出来,Hash分配到KeyGroup上,KeyGroup的数量对应着重分区Task的数量,最后把KeyGroup分配到对应的Task上。
5.2算子状态OperatorState
ListState:把所有状态的ListState收集起来,均匀分配给新的任务
Union List State:将List收集起来,广播到全部的任务,由任务决定去留
BrodCastState:广播状态都一样,直接拷贝到新的任务上
六、窗口
6.1时间窗口
有滚动、滑动、会话三种
6.1.1滚动窗口:Tumbling Windows
将数据依据固定的窗口长度对数据进行切分
时间对齐,窗口长度固定,没有重叠
6.1.2滑动窗口:Sliding Windows
滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成
窗口长度固定,可以有重叠
6.1.3会话窗口(Session Windows)
一段时间没有接收到新数据就会生成新的窗口,消息之间的间隔小于超时阈值(sessionGap)的,则被分配到同一个窗口,间隔大于阈值的,则被分配到不同的窗口
特点:时间无对齐
七、Flink组成
7.1JobManager
控制单个应用程序的执行,包括接收要执行的应用JobGraph和Jar文件、将JobGraph转化成ExecutionGraph、向ResourceManager申请资源启动TaskManager、分发应用给TaskManager执行、检查点CheckPoint工作
7.2ResourceManager
向集群资源提供者申请资源、释放资源
7.3TaskManager
1.注册Slot
2.启动任务
7.3Dispatcher
1.启动JobMaster
2.提供REST应用查询
八、高可用性
1.依赖Zookeeper来完成
1.1.活跃状态的JobManager将JobGraph和Jar的路径写到Zookeeper中
1.2.将CheckPoint的路径写入Zookeeper中
2.应用恢复,当活跃的JobManager发生故障,其下的应用都会取消
2.1新接手的JobManager请求Zookeeper,获取JobGraph和Jar文件以及CheckPoint
2.2重启应用
九、数据传输
1.在算子处理完数据后,为了不造成太大的网络压力,不会马上发送,会先收集到缓冲区中,以批次形式发送
2.每个TaskManager都有网络缓冲池,用于不同机器数据传输
3.如果接收端和发送端位于同一台机器内,序列化先放入缓冲区,缓冲区完毕放到队列中,接收任务获取数据再反序列化
4.如果发送端和接收端不在同一个机器,放入缓存后,先发送到TaskManager的网络缓冲池中,再进行发送
十、WaterMark
特殊的数据记录,必须单调递增
处理乱序事件,正确处理乱序事件,通常用WaterMark结合Windows实现
当时间戳达到窗口关闭时间,不应该立即触发窗口计算,等待一段时间,等待延迟再关闭窗口
水位线分配策略
周期性:以固定事件间隔来发出水位线并且推动事件前进。默认间隔为200毫秒
AssignerWithPeriodicWatermarks
定点水位线分配:根据输入事件的属性来生成水位线
AssignerWithPunctuatedWatermarks
十一、Flink检查点算法
基于Chandy-Lamport分布式快照算法来实现
该算法不会暂停整个应用,而是会把任务处理和检查点分离,这样在部分任务持久化状态过程中,其他任务还可以继续执行。
检查点的原理步骤:
1.由JobManager向Source数据源任务生成一个新的检查点编号,Source算子接收到信息后,暂停发出记录,利用状态后端触发生成本地状态检查点,状态后端保存完检查点后通知任务,随后任务向JobManager发送确认信息,随后恢复正常工作,然后生成特殊的CheckPoint Barrier记录,以广播的形式发送到下游任务。
2.当下游Transform算子接收到新的检查点分割符号,会暂停处理并且缓存当前流的数据,等待接收其他分区的检查点分隔符,所有分隔符到达后,通知状态后端生成检查点,保存通知JobManager后,向下游发送检查点分隔符CheckPoint Barrier后,继续处理数据。
3.Sink算子接收到分隔符后依次等待分隔符到齐后,生成快照并且写入检查点,向JobManager确认。
4.当JobManager确认已接受所有应用任务返回检查点确认消息后,将此次检查点标记为完成。
十二、Flink算子
12.1基本操作
1.map 对每个元素应用函数返回新的结果
2.filter 给出给定的条件过滤数据
3.flatMap 转换类似map,对每个输入产生零个、一个或多个输出事件,事实可以看作filter和map的泛化
12.2KeyedStream
从逻辑上将事件按照键值分配到多条独立的子流中
1.keyBy:指定的键值将一个DataStream转化为KeyedStream
2.滚动聚合:滚动聚合作用与KeyedStream上,它将生成一个包含聚合结果的DataStream,主要操作有:sum、min、max、minBy、maxBy
3.Reduce:滚动聚合的泛化,它将一个ReduceFunction应用在一个KeyedStream上,每个到来的事件都会和Reduce结果进行一次组合,从而产生一个新的DataStream
12.3多流转换
将多条流联合起来处理,或将一条流分割成多条流以应用不同逻辑。
Union:合并两条或多条类型相同的DataStream,生成一条新的类型相同的DataStream
Connect:接收一个DataStream并返回一个ConnectedStream对象
Split和Select:union转换的你操作。将输入流分割成2条或多条类型和输入流相同的输出流。
12.4分发转换
在使用DataStream API构建应用时,系统会根据操作语义和配置的并行度自动选择数据分区策略并且数据转发到正确的目标,返回的是DataStream。
shuffle:随机数据交换策略:均匀的分布随机将记录发往后继算子的并行任务
rebalence:轮流,将输入流中的事件以轮流的方式均匀地分配给后继任务
rescale:重调,轮流对事件进行分发,单局限于部分后继任务(一个Stream会指定1个或多个Stream均匀分发,而不是对所有Stream均匀分发),当接收端任务远大于发送端任务有效
broadcast:广播,将输入流的事件复制并发往下游算子
grobal:将输入流中的所有事件发往下游算子的第一个并行任务
十三、KeyedProcessFunction
作用于KeyedStream上,使用非常灵活,该函数会针对流中每条记录调用一次,实现了RichFunction接口,支持了open、close、getRuntimeContext,等方法,它还会提供以下2个方法
processElement:会针对流中每条记录都调用一次
onTimer:回调函数,注册的计时器触发时被调用
十四、触发器
决定了什么时候窗口准备就绪,触发计算,每个窗口都会分配默认的Trigger。比如EventTime的EventTimerTrigger,ProcessingTime的ProcessingTimeTrigger。
需要继承Trigger抽象类
onElement:在窗口中没进入一条数据时调用
onProcessingTime:根据ProcessingTime判断是否满足定时器的条件调用
onEventTime:根据窗口最新的EventTime判断是否满足定时器的条件
clear:在窗口清除时调用
前3个方法都会返回TriggerResult,其中包含了4个枚举值
CONTINUE:表示窗口不执行任何操作。即不触发窗口计算,也不删除元素
FIRE:触发窗口的计算,单保留窗口元素
PURGE:不触发窗口计算,丢弃窗口,并且删除窗口元素
FIRE_AND_PURGE:触发窗口计算,输入结果,并且清楚窗口数据
十五、基于时间的双流Join
15.1基于间隔的Join
基于时间的Join会对两条流中拥有相同键值以及彼此之间时间戳不超过某一指定间隔的的事件进行Join
也就是它只支持事件语义以及INNER JOIN
基于间隔的Join需要同时对双流的记录进行缓冲,缓冲的依据是配置的between时间戳,有2个变量,一个是下界,为负值,一个是上界,为正值。对于第一条流来说,时间戳大于当前水位线减去间隔上界的数据都会被缓存起来,对于第二条流而言,所有时间戳大于当前水位线加上间隔下界的数据都会被缓存起来。
15.2基于窗口的Join
基于窗口的Join原理是:将两条流输入流中的元素分配到公共窗口中并且在窗口完成时进行Join。具体的做法是:通过窗口分配器将2条流中的事件分配到公共的窗口内。当公共的窗口触发计算时,算子会遍历2个输入中元素的每个组合去调用JoinFunction。