Flink:下一代流计算平台?

简介

Flink是一个基于流计算的分布式引擎,以前的名字叫stratosphere,从2010年开始在德国一所大学里发起,也是有好几年的历史了,2014年来借鉴了社区其它一些项目的理念,快速发展并且进入了Apache顶级孵化器。

Spark以批计算的底层引擎同时支持批计算和流计算(把流切成小批),不同的是,Flink以流计算的底层引擎同时支持批计算和流计算,Flink提供了DataSet和DataStream的API,并在其上提供机器学习和图计算的库。最近,Flink也在发展SQL相关的API(Table),类似Spark中的DataFrame。

(此文默认读者有一定流计算基础,Storm经验)


优势

1.高吞吐且低延时

Flink:下一代流计算平台?_第1张图片
在简单case中,Flink吞吐可以是Storm的10倍,而延时在毫秒级(100ms以内)。
Storm以record by record的常驻任务模式,提供实时的计算,延时可在几十毫秒甚至10毫秒以内,但带来的代价是比较低的吞吐。Trident把数据流划成批次,但是执行起来仍然是Record级别的。所以Storm中,提高吞吐的有效方法是打包。
Spark Streaming不同于常驻任务模式,它将数据直接划成离散流,这样将流计算转换成了批计算,每一批都进行一次调度来运行,提高了吞吐,但是延时却依赖固定划成的时间段,时间段不能设置过小,因为每一批都会有任务调度等等的开销,一般Spark官方建议2-5s的时间段划分。
Flink:下一代流计算平台?_第2张图片
Flink同样提供了常驻任务的模式,并且为了克服RecordByRecord中吞吐量的问题,Flink发送数据时按照内存片来发送。内存片默认32KB,Flink把将要发送的数据序列化到内存片中,当内存片凑满或者超时,将此内存片发送出去,这里其实就有一个打包的逻辑,但是Flink不同于Storm的用户简单打包,Flink将数据直接序列化到受管理的内存byte数组中,这样这些业务对象很快被YoungGC掉了,不会导致FullGC频繁。
另外一方面,Flink的高吞吐也得益于它的序列化方式,通过DataStreamAPI,Flink了解到业务数据的Schema,从而生成定制化的序列化器,避免了昂贵的Kryo序列化(类型信息,各种反射)。
问题:打包即带来了一定延时(当数据量不大时会等超时Flush,当然可以将超时设置为0,这样每次写入都会把数据Flush),达到当拓扑很深时,延时会线性加大。但是Flink会把没有shuffle(Storm中的FieldGroup)的多个Transform组合优化到一起,所以以前在Storm上很多级的拓扑会在Flink中精简很多,拓扑不会很深。
(Storm 1.0 也做了打包相关的工作,但是思路并不是点到点的打包,而是DisruptorQueue的打包,性能能有3-10倍的提升)

2.Exactly-Once计算和状态管理

一般的ExactlyOnce语义要求状态的事务性,受Chandy-Lamport算法启发,Flink解决Exactly-Once的思路是定时进行一个一致性分布式snapshot的checkpoint,然后在出错后,恢复到上一个一致性的snapshot中,这与Trident的基本思路都是一致的,但是具体的实现却有一定区别。
Trident的事务分为两阶段:
1.并行的计算:这个阶段是单纯的计算,不牵涉对状态的改变,批之间没有关系。
2.状态的commit:将状态落地到外部存储中,这个阶段完全是串行的,所以外部引擎单纯的增量KV更新就可,不用多版本等概念(这里只讨论Transaction语义,不包括Opaque)。
Trident中是由Master控制整个计算,Master通知source发数据,才进行一批的计算,而Master并不知道当前是否有数据,这就导致了空批问题,当没有数据时,各种协调消息大量发送。对此,Trident添加了一个参数,限制批与批之间的最小间隔,但是这就直接导致数据延时取决于这个参数的设置(类似Spark Streaming)。

Flink事务:
与Trident不同,Flink的Master不控制什么时候发数据,它只控制什么时候划分批。
Master会发送Barrier控制流,将Source的数据分为一批批,而Source可以随时发送数据。
Flink:下一代流计算平台?_第3张图片
Master使用Barrier命令控制各个节点,从而生成一个个分布式快照(全量生成、多版本),Master会按一定策略清理无用版本(最新的savepoint甚至可以让用户向git一样管理这些分布式snapshot的版本)。
Barrier的传播类似Trident中的协调消息的传播,假如上游n个并发、下游m个并发,共需要n*m的消息量进行传播。具体过程参考下图:
Flink:下一代流计算平台?_第4张图片
但是这就造成一个问题:假如一个节点有并发A和并发B,它的下游节点有并发C。
1.当A处理完第1批把Barrier1发送给C,这时B还没处理第一批。
2.A已经处理完第2批把Barrier2发送给C,这时B还没处理第一批。
3.这时C已经拿到A发来的第一批和第二批的数据,这时它并不能处理第二批的数据,因为第一批的数据还不全,还不能构成一个分布式的一致性Snapshot,所以它需要把A发来的数据根据Barrier2来阻塞住。
所以关键还是分布式一致性要求数据的对齐!而Flink依靠Barrier这种机制来进行分布式一致性的对齐。
Flink:下一代流计算平台?_第5张图片
如图描述了节点的4个状态:
1.开始对齐,这时已经收到A的数据,但是B的数据还没来,需要对齐。
2.收到上游A来的下一批的数据,需要把这些数据阻塞住(也就是缓存起来)。
3.收完B发来的这批的数据,开始进行checkpoint,并发送Barrier到它的下游去。
4.继续下一批的对齐。
Barrier的发送完全是按照时间间隔来的,并且完全不影响数据的时延,用户可以根据恢复时间的需求来设置间隔。
Flink在checkpoint时会把state复制一份,然后进行异步的checkpoint,尽量不影响正常的计算。

问题1:异常后会导致Flink把整个作业ReDeploy(因为Barrier的存在不易设计rollback),但是Flink是线程模型,ReDeploy时并不会销毁进程,所以代价很低(类似SparkStreaming每批计算都会Deploy一次)。
问题2:状态不够灵活,强要求Key是shuffle中的那个key,这点上可以靠Hack代码来解决。
问题3:目前状态的checkpoint还是全量的,还不支持增量checkpoint。

3.逐级反压

流计算中数据的来源是不稳定的,有可能会突然出现大量的数据,这时会把整个拓扑给压挂(内存OOM等)。
所以流计算中需要有流控的手段,Storm中使用Spout MaxPending来进行流控。
问题是用户不容易设置这个值,设置小了,整个作业吞吐量上不去(没有达到完全流水线计算),设置大了,容易导致挂掉,从而被卡死。
Twitter在2014年的Storm论文中描述了一种auto-tuning的maxPending设置的机制,根据当前窗口ack回来的数据量对spout的maxPending参数进行动态调整。
而2015年Twitter提出Heron比较好的解决这一问题:使用反压,当下游节点撑不住时反压上游节点,从而让Source(spout)发送数据减少。
但是Heron和Storm一样,底层都是共享连接的方式,如果直接使用TCP反压,会导致分布式死锁的问题,所以它们都只实现了Spout反压(直接通知Spout)。
Flink:下一代流计算平台?_第6张图片
Flink的Task和Task之间独立一条TCP连接,这样Flink可以直接依靠TCP的反压。
坏处是底层连接数大量增加。
好处是能减少震荡问题,Spout反压中,当下游发生了拥塞,直接通知Spout,Spout停止发送数据。但是Spout的减小数据量会很久才能反应到下游,会有很久才能缓解,当停止反压后,Spout又发送一大批数据,又导致下游拥塞。这样的现象是节点在拥塞-空闲状态来回切换,严重影响吞吐。如果能直接反压上游,这样的震荡影响将会小很多(进一步精细的控制将会继续减小震荡)。

问题:依靠TCP来反压要求Task点到点建立单条连接,这样当并发较高时,连接数N*M的规模会太大,在这点上,Flink依靠较高的吞吐,可以让作业的并发设置不那么大。


4.基于YARN和线程模型

基于YARN就意味着可以进行资源的隔离,而且更易部署和管理,能和上面的存储引擎(Hadoop/HBase等)进行本地交互等等。
Flink运行作业分为两步:
1.创建AppMaster,申请资源并启动进程。
2.提交作业。
所以Flink运行作业的方式是线程模型,进程预先启动好,用户提交作业后,分配到各个进程上运行。这样设计的好处主要有两个:
1.大大加快了作业运行速度,作业如果分发好Jar包,启动速度是毫秒级别的。
2.作业之间可以通过JVM来共享一些数据,Flink最开始的想法就是让Batch和Stream共享一份数据(甚至包括查询)。
问题:线程模型使得各个Job共享JVM,也会导致它们相互干扰,在这点上,Flink把选择权交给了用户,用户可以选择公用一个FlinkApp,也可另起App。

5.灵活窗口计算

Flink中可以使用三种时间来进行窗口的运算:
1.EventTime:事件中自带时间字段。
2.ProcessingTime:进行窗口计算时的系统时间。
3.IngestionTime:事件进行Flink的时间(Source拿到数据的时间),可以认为是EventTime的一种。

在触发计算和LateEvent的处理中,Flink使用了Watermark的机制(WheelMill/Dataflow的概念)。
由于分布式环境中各个节点各个数据的时间都不确定,需要有一个统计的消息来确定时间,这就是Watermark的含义,它用来划分窗口的边界。
节点拿到watermark后,就可以根据此时间结束窗口的攒数据,计算输出并清除这个窗口,过后的lateEvent可以直接扔了或update历史值(具体逻辑都由用户定义)。
在实现方面,每个Source单独产生自己的watermark,下游节点收到watermark后,根据所有上游发来的watermark,使用最小的来当做自己的时间,并广播到下游。
Flink:下一代流计算平台?_第7张图片
(有点类似Barrier的感觉)

Flink的窗口很灵活,用户可以实现自己的窗口定义,也可直接使用TumblingWindow、SlidingWindow、SessionWindow。可以根据数据/内部定时器/watermark来定义触发输出的Trigger、触发清理数据的Trigger。
在窗口上,可以进行多个流的Join操作。
Flink会使用上文的stateCheckpoint机制来对窗口的数据进行容错处理。

Flink还在不断完善中,从模型上来说是要比Storm先进一些,进一步可参考官方文档及源码。

你可能感兴趣的:(java,spark,数据,分布式,Flink)