一、flink支持的时间概念类型
1.1、流计算与时间属性密不可分
相比较于离线计算,流计算往往离不开讨论时间这个概念,因为离线计算是有界数据,用户输入一个固定的数据集,离线计算引擎会输出一个固定的结果,这中间涉及的中间状态,包括时间的把握,用户都不操心。但是流计算是无界数据,并且实时引擎一直都在进行计算,正是因为如此,计算出来的结果每时每刻都在变化,因为数据量不可控,所以要关注状态,因为要保证计算的准确性,所以要关注事件属性。目前Flink支持的时间属性分别有Event Time,Ingestion Time,Processing Time,大致关系如下图。
1.2、事件时间(Event Time)
1.2.1、事件时间基本概念
事件时间是每个独立事件在产生它的设备上发生的时间, 简单来说这个时间在进入flink之前就已经存在,一般来讲都是使用消息创建的时间戳(当使用事件时间时,需要人为指定一个时间字段),正是因为这个关系,所以事件时间能够判断消息的有序性。基于Event Time的时间概念,数据处理过程当中,数据依赖于自己本身产生的时间,而不是Flink系统中Operator所在节点的系统时钟,这样能够借助于事件产生的时间信息来还原事件的先后关系,并且用事件时间统计出来的指标是最精确的。
1.2.2、事件时间能够解决乱序问题?
理想状态下,我们都希望数据流按照数据产生的先后顺序,进入flink系统,然后按顺序被处理,按顺序输出,但实际上,数据流大多存在乱序问题,这个乱序可能存在以下几个地方:
-》数据源-》flink系统
-》Source Operator-》transform Operator
-》transform Operator->Sink Operator
这也正是数据流从输入到输出必经的一条线路。
假设现在有一条乱序的数据流(10,9,8,5,7,6,4,3,2,1),很容易发现5这条消息延后了,如果现在我定义事件时间和滚动窗口结合使用,并且定义窗口大小为5,那么第一个窗口范围为(1-5),假设这里先不考虑水印(Watermark),当1,2,3,4消息落在了第一个窗口,当6消息到来时,flink判断6消息之前的消息都已经到达,则触发一个窗口开始计算,这样5这条消息都被丢失了,所以光靠事件时间并不能解决乱序问题,就像我刚才提及的水印,也只是能够再一定程度上保证不乱序,关于水印,下面会详细介绍。
1.2.3、为啥事件时间统计出来的指标是最准确的?
假设现在要计算一个指标,1:00-2:00的交易额,这里涉及到一个时间的区间,假设使用的是处理时间,那么窗口触发的依据将会是Operator的本地时钟,如果这时候消息发生乱序,很可能存在一部分数据本身时间戳是在1:00-2:00区间的,但是由于乱序,导致一部分数据没有落到这个窗口,那么这样计算出来的数据就不准确了。如果采用事件时间的话,面对乱序的数据流,我们结合着水印(Watermark),能够一定程度上解决乱序的问题,在使用事件时间时,会伴随着设置一个时间间隔,这个间隔也叫作对延迟到达数据流的容忍度,间隔越大,相对于来讲容忍度就越大。具体怎么解决,下面详解。
1.2.4、事件时间在什么场景下使用最合适?
事件时间一般用于对于时间很敏感的指标使用,例如上诉提及的1:00-2:00的交易额,有一个明确的时间区间,0:59的交易数据不能被统计,这种就适合用事件时间。如果对时间不是特别敏感,例如一些流量数据,可能只是做一些过滤等,这种就可以使用处理时间。从性能角度讲处理时间会比使用事件时间高很多。因为结合着事件时间的窗口,因为水印的存在,往往会晚触发窗口的计算,并且flink还要维护水印,这些都是额外的开销。
1.2.5、事件时间和窗口结合使用,窗口大小是不是绝对的?
假设使用事件时间+滚动窗口,并且窗口大小设置为10min,水印设置为1min,那么是不是这个窗口最晚11min以后会触发计算,其实不是这样的,如果换成摄入时间和处理时间的,那么确实是这样的。假象一下存在这样极端的场景(事实上确实存在),消息的数量出现坡峰,flink集群处于消费消息积压的状态,数据本身也有时间戳,那么10min大小的窗口,过了10min以后,并没有完全把本身时间戳是这10min内的数据都消费完,那么这时候由于窗口还没达到触发计算的条件(最新的消息时间戳-时间间隔>=窗口的上限),还是处于接受数据的状态,那么这个时间就大于10min了。说完了积压,当然也就存在另外一种情况,就是消息乱序的非常厉害,比如5min之后才应该到的数据,提前到了,会提前触发窗口进行计算,那么这样就小于10min了。所以说窗口大小不是绝对的。
1.3、摄入时间(Ingestion Time)
摄入时间(Ingestion Time)其实就是数据进入flink系统的时间,Ingestion Time依赖于Source Operator的本地时钟,Source Operator也算是进行流计算的第一道关卡,拿进入flink系统的时间作为后续窗口触发的条件,也能在一定程度上保证消息的有序性,上诉提到过,消息乱序有三个位置,事件时间+水印属于可以在一定程度上并且在三个位置都可以保证消息的有序性。而摄入时间(Ingestion Time)无法保证数据源-》flink系统这一段线路的有序性,哪怕是你乱序到达flink系统的,flink系统也会按照Source Operator的本地时钟来给你打上有序的时间戳。但是摄入时间(Ingestion Time)可以保证你在flink内部处理是有序的,因为流计算是分布式的,并且是多个算子相互协同计算,很可能内部出现乱序,并且偌大的flink集群,部分机器时钟不同步,网络延迟的,都是可能存在的,使用摄入时间(Ingestion Time),这个时间在进入flink系统也带上了,后续不会因为在不同的Operator进行处理而被修改。
1.4、处理时间(Processing Time)
处理时间就相对好理解一点,处理时间就是数据在进行操作算子计算的过程中所在的那台执行机的本地时钟,由于flink集群存在很多执行机,那么都会单独维护自己的处理时间。使用处理时间是完全不能够保证消息的有序性的,所以对时间准确性有要求的话,不要使用处理时间。处理时间的优点就是性能会相对好一些。
二、EventTime和Watermark
2.1、概述
Flink支持EventTime这个时间属性,相对于其他流式计算框架,算是一大优点。EventTime和Watermark主要就是为了解决,在面对消息存在乱序的情况下,尽可能的保证每条消息能够准确的落在所属的窗口,即使你是延迟到达,这样子才可以保证每个窗口数据的完整性,最红指标的准确性。Flink会用最新的事件时间减去固定时间间隔(用户自己设置的)作为Watermark,该时间间隔为用户外部配置的支持最大延迟到达的时间长度,也就是说不会有事件超过该间隔到达,否则就认为是迟到事件或者是异常事件。
2.2、顺序事件中的Watermark
理想状态下,如果数据元素的事件时间是有序的,那么水位线也会随着时间逐渐增长,直至触发窗口计算。
如上图所示,窗口大小为5,时间间隔为1,那么当接收到6这个事件时间以后,6-1=5>=5,则触发上一个窗口进行计算。可以发现在有序的数据流面前,Watermark只是起了一个标记作用,反而会因为容忍的时间间隔,导致窗口晚计算。
2.3、乱序事件中的Watermark
在现实生活中,数据元素往往不是有序的,这时候就需要Watermark出场,见下图:
上图的窗口大小为20,时间间隔为4,可以发现当24进入flink以后,水位线等于24-4=20>=20,满足窗口触发条件了,但是这时19这个事件时间还未进入这个窗口,这就表示被丢弃了。通过这张图说明,容忍的时间间隔短了,如果设置成5,那么19就不会被丢弃,但是这种需求往往是无止尽的,我们在做流计算时,应该要想着如何去解决数据流产生乱序的根本原因,而不是一味的增加容忍时间。
2.4、并行(分布式)数据流中的Watermark
上述例子都是单个task来解释,这是为了帮助前期好理解,但实际情况上并不是这样的,Flink是一个分布式的流式计算框架,必定存在单个算子,也有多个task并发运行的情况,所以实际情况见下图:
Watermark在Source Operator中生成,并且在每个Source Operation的子Task中都会独立生成Watermark。在Source Operator的子任务中生成后就会更新该Task的Watermark,且会逐步更新下游算子中的Watermark水位线,随后一致保持在该并发之中,直至下一次的Watermark的生成,并对前面的Watermark进行覆盖。如图所示,黄色的小框框标的都是当前这个算子子任务的Watermark。仔细看其实会发现上下游关系存在多对一或者一对多,那么逐步更新下游算子中的Watermark水位线时就会出现多个Watermark同时更新一个算子Task的当前水位线,这时Flink会选择最小的水位线来更新,这也是为了极大的保证数据不会因为延迟而被丢失了。
三、单纯延迟和乱序对流计算的影响
3.1、单纯延迟
单纯延迟指的是,数据还是有序的,只不过因为各种原因来得非常慢,或者数据量很大,flink消费消息造成了积压,这种都可以称作为单纯性的延迟,如果这种使用摄入时间或者处理时间,对于对时间区间有严格要求的指标,就完成不能够保证了。如果使用事件时间的话,那么造成的直观影响就是每个窗口触发计算的时间都会延迟(相对于绝对时间),其实刚才也有介绍过,那么这种情况,直观的影响就是数据产出非常慢,无法达到时效性。
3.2、乱序流
如果是数据流乱序了,那么可以使用事件时间+Watermark,可以在一定程度上保证乱序的数据流也能落在正确的窗口上。
四、如何科学的指定Watermark
之前有介绍过事件时间+Watermark可以在一定程度上保证数据流乱序的准确性,那么这个准确性如何,以及设置完Watermark以后,flink运行的性能如何,其实对这个Watermark的设置非常有考究。
一般在进行流计算任务开发的时候,如果有用到事件时间,都会手动设置一个Watermark,也称之为容忍度,但是这个时间过短,可能还是会造成一部分数据丢失,过长,会导致窗口触发计算时间延后,对性能影响较大,所以这个时间间隔,需要科学的测量。
一般来讲,使用事件时间,数据本身就有一个时间戳,这个时间往往是数据产生的时间,我们在flink任务当中,当数据流进入flink系统以后,手动加一下时间戳,这样就得到两个时间戳了,我们可以让测试任务上线跑一段时间,然后将这一段时间的数据落到关系型数据库,进行简单的数据分析,可以看出最大的延迟消息延迟了多少,百分比啥的,都是可以分析出来的,有了这个根据,在来设置时间间隔,就更加运筹帷幄了。