Flink WaterMark和Lateness的工作原理

1 篇文章 0 订阅
1 篇文章 0 订阅

一、前言

之前在做业务的时候,对于Flink对事件时间的处理原理进行过源码分析,但当时主要精力在业务上,并没有对flink内部的处理原理进行深入的分析和记录。并且当时对于WaterMark和Lateness的区别,并没有完全吃透,甚至可以说脑子里对这两个概念是一团浆糊,不知道已经有了WaterMark,为什么还要设置Lateness这个东西?因为WaterMark的设置就是为了处理乱序的数据,而Lateness的作用也是为了给乱序数据一个缓冲时间。最近闲来无事,再次深入flink的源码,想把这一块儿东西吃透。下面主要从WaterMark和Lateness源码级别看看他的执行流程,做一下记录。在文章最后,再根据自己的理解,谈谈为什么Flink要使用这两个东西来保证数据乱序的处理!

二、源码

1、预先需要知道的关于flink的一些知识

Flink的窗口操作主要有Assigner、Trigger、Evitor

Assinger主要用于判断一个元素应该落在哪个窗口内

Trigger主要用于判断是否触发窗口数据的计算

Evitor主要用于进行窗口数据的清除(暂时没有仔细研究)

关于Trigger的返回结果及含义如下:

FIRE:触发数据计算

PURGE:清除窗口内的数据

CONTINUE:不做任何处理

FIRE_AND_PURGE:触发计算,计算完成后清除窗口内的数据

我们本篇默认使用事件时间的EventTimeTrigger触发器作为分析对象,EventTimeTrigger源码如下(只列出onElement和onEventTime两个函数,因为我们分析只用到这两个函数):

public class EventTimeTrigger extends Trigger<Object, TimeWindow> {
	private static final long serialVersionUID = 1L;

	private EventTimeTrigger() {}

	@Override
	public TriggerResult onElement(Object element, long timestamp, TimeWindow window, TriggerContext ctx) throws Exception {
		if (window.maxTimestamp() <= ctx.getCurrentWatermark()) {
			return TriggerResult.FIRE;
		} else {
			ctx.registerEventTimeTimer(window.maxTimestamp());
			return TriggerResult.CONTINUE;
		}
	}

	@Override
	public TriggerResult onEventTime(long time, TimeWindow window, TriggerContext ctx) {
		return time == window.maxTimestamp() ?
			TriggerResult.FIRE :
			TriggerResult.CONTINUE;
	}
}

在本篇文章的介绍中,我所使用的的例子,使用的是一个滚动窗口,并且Trigger分析的是默认的EventTimeTrigger(因为Trigger是一个非常灵活的接口,可以进行自定义,甚至什么时候去触发窗口操作也是我们可以手动设置的。为了更好的窥探Trigger的原理,直接使用原生的EventTimeTrigger)

这里列一下EventTimeTrigger中两个函数上游的调用过程,除了Trigger之外,下面拆解的流程中,窗口的生成,数据的处理,数据的删除和触发器的触发等,都是在WindowOperator中。

 

2、窗口及场景设置

程序中我们设置滚动窗口的大小为10s,WaterMark的乱序时间是30s,允许的Lateness的时间是10s,场景图像如下:

所谓WaterMark的乱序时间是30s,在代码中的体现为:waterMark = element.maxTimestamp - 30s,也就是当前获取到数据的最大时间戳减去允许乱序的时长

代码样例如下图:

3、流程拆解

      a、element.MaxTimestamp = 5s ;  waterMark = -25s:

            

                 1、 如果此时过来一条时间戳属于窗口内的数据e1,通过waterMark <= window.MaxTime + lateness逻辑判断窗口是否失效,此时窗口并未失效,所以将e1可以放到窗口内等待计算

                 2、每收到一条数据,系统都会调用EventTimeTrigger的onElement函数(从windowOpeartor中调用),判断是否要进行数据处理。从代码中看到,如果window.maxTime <= WaterMark,才会触发操作。此时的场景window.maxTime(10s) > waterMark(-25s),所以不会触发窗口计算;但是此时会注册一个触发器ctx.registerEventTimeTimer(window.maxTimestamp()),这个触发器是干嘛用的呢?这个触发器的意思是告诉系统,当waterMark > window.maxTime的时候,要调用onEventTime函数。

                3、调用onElement之后,windowOperator中默认也会给当前窗口注册一个触发时间:ctx.registerEventTimeTimer(window.maxTimestamp() +  lateness)

                所以此时在系统内部有两个调用onEventTime的时间点:waterMark >= 10s(window.maxTime) 或者 waterMark >= 20s(window.maxTime(10s) + lateness(10s))

                此时窗口的状态:未触发数据计算(窗口中有元素e1)

                                             未删除窗口内的数据

                                             触发器两个:waterMark >=  10s 或者 waterMark >=  20s

                注:每次判断符合触发器的条件之后,系统在调用onEventTime函数之前都会将触发器删除,具体代码在:org.apache.flink.streaming.api.operators.InternalTimerServiceImpl的advanceWatermark(long time)中的eventTimeTimerQueue.poll()

      b、element.MaxTimestamp = 15s ;  waterMark = -15s :

         

                 1、 如果此时过来一条时间戳属于窗口内的数据e2,通过waterMark <= window.MaxTime + lateness逻辑判断窗口是否失效,此时窗口并未失效,所以将e2可以放到窗口内等待计算

                 2、e2放到窗口中后,再次调用EventTimeTrigger的onElement函数,判断是否要进行数据处理。从代码中看到,如果window.maxTime <= WaterMark,才会触发操作。此时的场景window.maxTime(10s) > waterMark(-15s),所以不会触发窗口计算;同理a,再次注册触发器。因为这次注册的时间和a中注册的时间都是window.MaxTime,在数据结构底层其实会覆盖掉,只保留一次。

                 3、同理a,也会再注册另一个触发器:ctx.registerEventTimeTimer(window.maxTimestamp() +  lateness),因为数值相同,会覆盖掉a中的数据,保留一次。

                 4、因为onEventTime函数的触发条件(waterMark >= 10s 或者 waterMark >=  20s)不满足,故不会调用onEventTime函数

                 此时窗口的状态:未触发数据计算(窗口中有元素e1,e2)

                                             未删除窗口内的数据

                                             触发器两个:waterMark >=  10s 或者 waterMark >=  20s

      c、element.MaxTimestamp = 25s ;  waterMark = -5s :

         

                   1、 如果此时过来一条时间戳属于窗口内的数据e3,通过waterMark <= window.MaxTime + lateness逻辑判断窗口是否失效,此时窗口并未失效,所以将e3可以放到窗口内等待计算

                   2、因为此时并未触发计算,所以此时除了在窗口内增加了一个e3元素外,其他的处理逻辑和a、b是一样的。

                   3、因为onEventTime函数的触发条件(waterMark >= 10s 或者 waterMark >=  20s)不满足,故不会调用onEventTime函数

                   此时窗口的状态:未触发数据计算(窗口中有元素e1,e2,e3)

                                               未删除窗口内的数据

                                               触发器两个:waterMark >=  10s 或者 waterMark >=  20s

      d、element.MaxTimestamp = 35s  ;  waterMark = 5s :

        

                   1、 如果此时过来一条时间戳属于窗口内的数据e4,通过waterMark <= window.MaxTime + lateness逻辑判断窗口是否失效,此时窗口并未失效,所以将e4可以放到窗口内等待计算

                   2、因为此时并未触发计算,所以此时除了在窗口内增加了一个e4元素外,其他的处理逻辑和a、b、c是一样的。

                   3、因为onEventTime函数的触发条件(waterMark >= 10s 或者 waterMark >=  20s)不满足,故不会调用onEventTime函数

                   此时窗口的状态:未触发数据计算(窗口中有元素e1,e2,e3,e4)

                                               未删除窗口内的数据

                                               触发器两个:waterMark >=  10s 或者 waterMark >=  20s

      e、element.MaxTimestamp = 40s ;  waterMark = 10s:

        

                   1、 如果此时过来一条时间戳属于窗口内的数据e5,通过waterMark <= window.MaxTime + lateness逻辑判断窗口是否失效,此时窗口并未失效,所以将e5可以放到窗口内等待计算

                   2、调用EventTimeTrigger的onElement函数,因为此时window.maxTime <= waterMark,所以onElement返回FIRE,触发计算,但是不清除数据 (触发了计算)

                   3、同时onEventTime函数的触发条件也满足(waterMark >= 10s),故调用onEventTime函数(调用之前会把waterMark >= 10s条件删除)。onEventTime判断注册的触发时间(10s) = window.maxTime,再次返回FIRE,触发数据计算。

                   4、在触发了onEventTime函数之后,WindowOperator类中会判断要不要删除窗口中的数据,判断条件是:

                   

                       这个内部的判断原理是:timer的触发时间(此时是10s)是否等于window.maxTime + lateness,显然此时不相等,故不会进行数据的清理。

                   此时窗口的状态:触发数据计算(窗口中有元素e1,e2,e3,e4,e5)

                                                未删除窗口内的数据

                                                触发器:waterMark >=  20s

      f、element.MaxTimestamp = 45s ;  waterMark = 15s:

        

                  1、 如果此时过来一条时间戳属于窗口内的数据e6,通过waterMark <= window.MaxTime + lateness逻辑判断窗口是否失效,此时窗口并未失效,所以将e6可以放到窗口内等待计算

                   2、调用EventTimeTrigger的onElement函数,因为此时window.maxTime <= waterMark,所以onElement返回FIRE,触发计算,但是不清除数据 (触发了计算)

                   3、此时只剩一个触发器了(waterMark >=  20s),触发条件不满足,故不会触发onEventTime操作

                   此时窗口的状态:触发数据计算(窗口中有元素e1,e2,e3,e4,e5,e6)

                                                未删除窗口内的数据

                                                触发器:waterMark >=  20s

      g、element.MaxTimestamp = 50s ;  waterMark = 15s:

        

                   1、 如果此时过来一条时间戳属于窗口内的数据e7,通过waterMark <= window.MaxTime + lateness逻辑判断窗口是否失效,此时窗口并未失效,所以将e7可以放到窗口内等待计算

                   2、调用EventTimeTrigger的onElement函数,因为此时window.maxTime <= waterMark,所以onElement返回FIRE,触发计算,但是不清除数据 (触发了计算)

                   3、此时触发器(waterMark >=  20s)触发,调用EventTimeTrigger(调用之前会删除触发器)的onEventTime函数,但此时onEventTime函数判断注册的触发时间(20s) != window.maxTime,返回CONTINUE,不会进行数据计算。(未触发计算)

                   4、在触发了onEventTime函数之后,WindowOperator类中会判断要不要删除窗口中的数据,判断条件是:

                       这个内部的判断原理是:timer的触发时间(此时是20s)是否等于window.maxTime + lateness,此时相等,进行数据的清理 (进行数据的清理)

                   此时窗口的状态:触发数据计算(窗口中的数据删除)

                                                删除窗口内的数据

                                                触发器:无

h、element.MaxTimestamp = 55s ;  waterMark = 25s:

 

 1、 如果此时过来一条时间戳属于窗口内的数据e8,此时waterMark <= window.MaxTime + lateness逻辑判断窗口是否失效,此时窗口已经失效,所以不再接受e8数据,e8数据会被丢弃

4、总结

(1)、我们上面分析的Trigger是系统自带的EventTimeTrigger,这个Trigger默认是窗口聚集了一批数据之后再进行数据处理(onElement函数返回的Continue),但是对于那些介于waterMark和Lateness之间的数据,是来一条处理一条(onElement函数返回的FIRE)

(2)、EventTimeTrigger会手动设置一个触发器(onElement函数ctx.registerEventTimeTimer(window.maxTimestamp())),如果没有设置的话,其实系统只有在waterMark = window.maxTime + Lateness时才会进行窗口的触发操作

(3)、Trigger的使用非常灵活,如果我们自定义,可以设置为来一条处理一条,不用等到触发器触发的时候再进行数据处理,而且如果我们的process里面保存有状态信息的话,完全可以处理一条删除一条(当然我说的这种是对于比较简单的场景,如sum统计等),这对于非常大量的数据,可以减少数据对内存的使用。

5、WaterMark和Lateness的区别

在我看来,在实际场景中,如果有人想要WaterMark和Lateness两个配合使用,大概原因在于:

(1)、在WaterMark规定的延迟时间内,我必须要有一个结果输出出来。

(2)、在WaterMark触发了窗口操作之后,若还想再缓冲一段时间进行延迟数据的处理,不让窗口失效,那就再加上一个Lateness

如果不是上述原因,超过一定时间的数据我就丢弃,其实我觉得使用其中一个就能满足条件,不需要同时设置两个。

当然也有另外一种解释(针对默认的EventTimeTrigger):

(1)、waterMark用来标识什么时候开启窗口计算。

(2)、lateness用来标识什么时候销毁窗口。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值