00:00
到目前为止,我们已经了解了flink当中data stringpi的基本用法,哎,我们知道了怎么样去创建执行环境,怎么样使用原算子去读取数据源,然后怎么样进行转换处理计算,最后怎么样输出到外部系统当中,这样的话,我们其实就已经可以写出一个完整的link流处理程序了。当然了,对于弗link而言,我们说它是有状态的流处理,它还是一个分布式的大数据处理引擎,所以他能做的事情远远不止这些,Flink能做的事情非常的多,非常强大啊。那一个典型的应用呢,就是我们可以。在数据流上去开窗口啊,简单来讲就是我们当前的流,它不是一个源源不断无穷无尽的流吗?无界流,那么如果说我们想要统计所有数据的话,往往不太现实,也没有意义,我们关心的可能就是某一段时间内。
01:00
所有数据的统计结果到底是什么样?所以这就相当于开了一个时间窗口,划定了一个时间范围,统计这段时间内所有数据,它的结果是什么样?所以我们发现啊,窗口的定义其实跟时间是密不可分的,所以呢,接下来这一章我们要介绍的就是flink当中的时间和窗口。那首先我们来讨论一下flink当中的时间语义啊,大家可能会觉得有点奇怪,对于时间来讲,这有什么好讨论的呢?时间不就是一个非常简单的概念,我们平常日常生活当中是用表或者钟来进行度量的,那在计算机系统当中,这不就是系统时间吗?机器时间吗?诶,我们只要调用system里边的对应的方法,就可以获取到当前的时间是什么样的。这里我们之所以要讨论时间语义,其实主要的原因就在于我们现在的flink是一个大数据处理引擎。
02:02
它的最大特点就是分布式架构,它是分布式的集群,所以对于分布式的集群而言,每一个机器都有它自己的一个时钟。它是有自己的机器时间的,那这个时候我们整体来看的话,这个集群当中的时间又以谁为准呢?这其实是分布式系统的一个最大的问题,就是我们没有全局统一的始终啊。那有同学可能想,我们不是有一个job manager,他是集群的管理者吗?以它作为标准,给不同的task manager去发出指令,只是提示当前的时间可以不可以呢?呃,当然可以这么去做,但是我们知道在这个过程当中也会有消息传递的延迟,那我们想保证绝对的同迟是不可能做到。另外还不仅仅是这个问题,还有一个问题就是我们当前做数据处理,做数据分析,其实数据还要从外部去读取啊,就像前面我们所说的啊,调用原算子,如果说我们从卡夫卡里面去读取数据的时候,首先这个数据产生之后,先要进行采集,收集好了之后呢,先要放到消息队列里面,然后再进行传递,Flink当中我们使用flink卡夫卡consumer去进行读取,读完之后呢,我们还要在不同的操作算子、转换算子之间去进行传递,所以我们发现这个过程当中,数据传递它又会有很大的延迟。
03:29
我们数据产生的时间和最后进行处理的时间显然不是一回事,所以我们到底应该以哪个时间为准呢?这就是我们要讨论的时间语义的概念。所以接下来我们可以用这样一张图来做一个完整的梳理,我们看一看我们现在所要处理的分布式的流数据,到底面临的是一个什么样的状态。首先我们看到诶一个数据的产生,那其实是标志着某一个事件的发生啊,我们知道对于电商这个场景下,我们知道用户在这个网站上有各种各样的点击操作,我们可能要收集用户的这些行为数据啊,那比方说用户在我们当前一个商品页面上做了一个点击。
04:15
接下来我们在页面上把这个信息收集到之后,传给当前的后台服务器,服务器就会把它写入到日志事件里边。写入日志之后,那接下来呢,我们可能会对数据进行采集,然后把它传递到消息队列里面去啊,比方说这里就是卡夫卡,卡夫卡我们知道它也有不同的分区,那所以现在已经是一个分布式的采集和传递的过程了,然后接下来呢?啊,我们接下来从卡夫卡里面读取数据,进入到flink系统当中来进行分布式的处理,那首先是原算子,原算子这里把数据进行一个分布式的读取,接下来又可以传递到后续的处理算子里边,比方说现在我们要开窗口的话,那就是一个窗口算子。
05:05
当前我们还是可以有并行的子任务,所以这还是一个分布式的传递啊,所以我们看到啊,真正在做计算的时候,是窗口算子这一步操作,在当前节点上去处理数据,如果说我们这个时候去捕获系统时间的话,那应该是这一时刻的时间,它跟数据产生,事件发生的时间完全是两回事。啊,那这就是我们所说的啊,假如说我们想开一个窗口,统计早上八点到九点的所有数据,那假如说有一个数据,它产生的时间是08:59:59。那按照我们的想法,这个数据应该收到当前的这个窗口里面来。假如说我们经过这样一个分布式传递之后呢,它有延迟到达窗口算子进行处理的时候,已经变成了09:00:01。
06:02
那这个时候是不是我们的系统时间就已经窗口关闭,不会把这个数据进行处理了呢?哎,确实是这样,所以我们会发现啊,当前的这种分布式架构里边就会出现这样的一个问题,所以我们一定要明确窗口进行数据的收集,这个时间到底是以谁为标准的。所以我们看到啊,当前事件发生的这个时间,我们就可以把它叫做事件事件。那当前对数据进行处理,我们当前的处理算子所在节点的系统时间,这就叫做处理时间。这是两个完全不同的时间语义。那相对于我们前面事件发生的时间来看呢,数据进行处理的时间往往会有一定的延迟。所以在flink当中就给我们提供了这两种完全不同的时间语义啊,那如果说我们当前的时间语义设置为处理时间,这种方式就非常简单了,那就是假如我们开了一个八点到九点的窗口,我们关闭窗口的标准就是当前算子所在的这个节点,它的系统时间达到九点钟啊,那假如说数据是08:59:59产生的,到达时间的时候已经是9.01秒了,诶,那么处理的时候是9.01秒,这个时候这条数据就不会进入窗口收集进行计算了。
07:26
这种方式其实就比较简单粗暴啊,就是只以当前的系统时间为准嘛,啊,那我们可能会想到,呃,它就不够准确,因为当前的窗口算子还是分布式的,那假如说我们这个分布式传递的时候,有可能耗费的时间不一样,那就会出现什么情况呢?同样都是08:59:59产生的数据,两条数据,一条数据传到了这个算子,另外一条数据呢,传到了窗口算子的并行子任务里边来,那这个时候有可能一个数据过来的时候还在九点之前,那另外一个呢,就有可能到了九点之后,所以我们会发现啊,这就没准,有时候可以正常的统计,有时候就不能统计正确了,所以这种方式往往是不符合我们实际应用需求的啊,那另外一种时间语义呢?就是所说的事件时间,事件时间就是指每个事件发生的时间,也就是说数据产生的时间。
08:24
那我们怎么样收集每个事件发生的时间呢?啊,其实我们知道啊,一开始在写入日志文件的时候,我们一般都会带着一个时间戳time step啊,那所以这其实就表示当前数据到底是在哪一时刻发生的,所以接下来如果在这个事件时间语义下,我们的判断标准呢,就不再是本地的系统时间了。八点到九点的这个窗口什么时候关呢?诶,那就是要以当前事件发生的时间已经达到了九点钟为止,这个时候我们就把八点到九点的窗口关闭,在这之后到来的数据,我们就认为都应该是九点之后的数据了。
09:08
所以这个关键就是我们需要从所有的数据里边提取它的时间戳,然后就把它当成我们当前的一个时钟,呃,就是每个数据发生的时候,第一秒的数据带着第一秒时间戳的数据来了,我们就认为当前的时间进展到一秒,那然后第二秒的数据来了,我们就认为当前的时间进展到了二秒啊,那所以我们会发现这种方式的话,跟系统时间就没有任何的关系了,他只跟当前事件里边代指的时间戳有关。啊,当然我们会发现啊,这其实也有另外一个问题,就是如果说我们当前的这个数据都是按照时间戳从小到大,就是它是按照顺序一个一个排好了来的,那我们的这个时间就可以不停的推进,可以一直朝前走,但是假如说经过前面的分布式传递之后。
10:00
当前的这个数据带的时间戳发生了乱序,发生了打乱,啊,就是两秒之前有可能来了一个三秒的数据,那这个时候怎么办呢?啊,那这个时候我们就不能简单的把当前的时间戳直接作为事件时间的进展了,这就会引入另外一个概念,就是所谓的水位线,这个我们放到后边再去详细讲解。啊,那这就是关于处理时间和事件时间的一个基本概念,那我们会想到啊,在实际应用过程当中,到底应该使用哪种时间语义呢?啊,其实按照前面的描述我们已经发现了啊,应该是事件时间用的更多一点啊,因为很明显我们更关心的是当前数据产生的那一时刻,它到底是什么时候。至于说诶,等我们收集到它要处理它的那个时刻,到底是什么时间,其实我们并不关心嘛,啊,但是对于实际应用场景来说呢,其实每种时间语义都有它自己的应用场景,比如说我们这里可以举一个生活当中的比较经典的例子,那就是星球大战这一部电影啊啊那我们知道对于这个很多经典电影而言,它一开始如果第一部电影啊叫好又叫座拍的特别好的话,那往往后边还会拍续集啊,而且我们知道啊,这种拍成系列电影之后,它会构建一个完整的世界,所以不光要拍续集,还要拍前传,所以我们看看这个星球大战就是一个典型的例子啊啊那。
11:31
他的第一部其实是1977年就已经拍摄了,拍完了之后呢,80年83年就接着拍了两部续集。拍完了之后,哎,时隔十几年之后,他又开始拍前传,99年02年05年又拍了三部前传,然后到一五年的时候呢,又接着之前的故事线又拍了一部续集啊啊,那当然了,后面我们知道现在应该也已经有这个星战八啊,也已经拍出来了啊,所以我们会发现所有的这个时间和它故事发生的时间啊,就是当前电影拍摄上映的时间和故事发生的时间,这是两条时间线。
12:08
我们如果看这个星球大战电影的话,按它上映的时间来看的话,诶,那当然就是先拍出哪个来,我们就看哪一部,但是如果说我们想要完整的了解当前他故事发展的时间顺序的话,那其实应该是先去看九九年上映的这个。幽灵的威胁,诶,这按时间顺序来看,这是星球大战的第一部,然后呢,呃,是零二年和零五年的这两部前传,接下来再翻回头去看七七年所拍摄的456啊,最后是2015年拍摄的星战七。哎,所以我们看不同的观影顺序,其实是代表了我们不同的风格,不同的需求,如果说你是一个呃,想看这个科幻故事,想看这个剧情的话,那很显然我们按照它事件发生的时间,本身故事发生的时间去看,这个可能是最好的观影顺序啊,那假如说我们是主要还是喜欢看这个电影特效啊,我们喜欢看这个电影发展的过程,那就按照电影拍摄的时间去看就可以了,所以我们看啊拍摄时间,这就相当于是处理时间,而我们的本身故事发生的时间,这就相当于是事件事件。
13:20
那选取哪种时间语义,这其实跟我们具体的需求是有关的,在实际的这个大数据处理场景里边呢,那一般我们就不会去考虑当前处理的这一时刻是什么时候了,我们更关心的是事件发生的时间啊,比方说我们计算网站的PVUV,诶,非常经典的一些指标,统计每天的访问量的时候,诶,那很显然我们统计的是用户真实的访问时刻到底是什么时候。并不是说我们收集到数据之后,要处理的时刻到底是什么时候,诶,那所以我们当然是以事件时间为准,我们一般都是要从业务日志里面去提取时间戳,用它来表示当前的时间时间。
14:04
那所以整体来讲的话,事件时间就是我们一般情况更符合业务处理逻辑的时间语义,所以对于我们现在的link系统而言,默认使用的时间语义就是事件时间,那这么说的话,是不是处理时间就没有意义呢?其实也不是啊,因为在实际场景里边,我们知道事件时间作为判断标准,它是要提取数据的时间戳的。那时间戳呢,我们说诶在分布式传输场景下,它又有可能出现乱序,那如果出现乱序怎么办呢?诶,那我们就得多等一会儿啊,简单来讲的话就得多等一会儿,等之前混乱的数据都已经都到齐了,这个时候我们再进行处理,那这样的话很明显我们就需要额外的等待,就有可能在数据量非常大的时候,堆积数据处理不完啊,这就产生了被加,而这个时候如果说我们直接使用处理时间的话,那他就不管我们这个数据到底乱不乱码,他根本不考虑数据里边的时间戳,只是。
15:03
按当前的系统,时间来了的就计算,没来的那就相当于是下一个时间窗口了,所以处理时间其实在处理的过程当中会提高计算的效率,降低处理的延迟,哎,所以这就看我们真实场景下的具体需求了,如果说我们是希望当前处理的压力更小,也就是计算要更快,要有更低的延迟,那我们可以牺牲一点处理的逻辑的准确性,然后去使用处理时间。那如果说我们对于计算结果的准确性要求非常高,那就不能用处理时间啊,因为他可能统计出来的结果不正确嘛,所以我们就只能使用事件时间。所以我们可以说整体来看的话,事件时间语义就是以一定的延迟为代价,换来了处理结果的正确性啊,那另外呢,在弗当中还提供了一个摄入时间的概念啊,这个其实也比较简单,他如果看我们这张图上的话,就是数据进入到弗link系统当中,也就是在数据原算子读取数据的时候,这个时候的系统时间啊,那有时候啊,如果说我们获取不到事件真实发生的时间啊,获取不到这个时间戳的话,往往可以用这个摄入时间来代替事件时间做一个近似。
16:21
这个用的并不是特别的多啊,所以我们一般情况下使用的就是事件时间或者处理时间啊,那我们知道处理时间其实比较简单,所以在早些时候的flink版本当中呢,它默认的时间语义其实是处理时间。那考虑到了事件时间在实际应用当中其实是会更加的广泛一点啊,我们对于结果的正确性可能要求会更高一点,所以是从01:12版本开始之后,Flink默认的时间语义变成了事件事件,这就是关于时间语义的概念。
我来说两句