Flink之究极通俗详解Watermark与EventTime

运营小姐姐:我想要个实时模型每隔5秒统计下最近10秒的呼出失败用户~
Flink的优势之处在这此类实时统计中就体现出来了,在此类问题中,Flink引入了时间语义和Watermark水位线来解决此类的问题和该场景出现时无法避免的乱序问题。
在说明Watermark是什么之前,我们先讨论一个基本的实时处理问题(假设在读这篇文章的同学已经了解了Flink的时间语义哈),即如果我们只有ProcessingTime时间语义的处理系统我们解决此类问题的方式和可能出现的问题
我们先for example下,根据运营小姐姐的要求,我们肯定先需指定一个大小为10s,步长为5s的窗口:

//ProcessiongTime语义统计 (不显示指定,Flink默认时间语义就为ProcessingTime)
val datastream = senv.socketTextStream("localhost", 8888)
	.map {(m: String) => (m.split(",")(0), 1) }
    .keyBy(0)
    .timeWindow(Time.seconds(10), Time.seconds(5))
    .sum(1)
    .print

1.ProcessingTime处理顺序数据和乱序数据

为了简单理解和示例代码简化,假如上面我们拿到已经都是呼出失败用户数据流,那么假如源系统(在此例中源系统可以当作基站)在第6秒产生了两条失败数据m m 和第11秒产生了一条失败数据 m ,就像下面这样:
在这里插入图片描述
那么按照ProcessingTime语义和数据按部就班到达(顺序到达)这种情况来说,这三条数据放在window的情况就像下面这样:
在这里插入图片描述
没毛病,三个窗口触发时都做出了正确的计算。但问题就在于,这种理想情况在实时数据流中真的就是理想,在实际应用场景中实时数据流基本都是乱序的,出现的原因有很多种,拿运营商的此类数据来说,要么基站那一会抽风数据卡住没法出去,或者数据在整个流动过程中出现网络延迟或者背压,或者被分散到了不同的topic,那么到达flink时,都是乱序的。
所以假如原本应该在第6秒到达原定窗口的其中1条数据m因为各种原因延迟了,到达时已经是第16秒了,那么情况如下:
在这里插入图片描述
那么,我们基于ProcessingTime语义的窗口,就会出现统计错误,原本应该在window1被处理,属于在第5秒产生的数据m却被window2,window3(因为滑动步长为5,滑到15秒时,在第16秒延误的m是属于window3的)处理统计了。此时运营小姐姐:垃圾开发~
所以,这时基于ProcessingTime语义的计算就无法满足场景的要求,EventTime和Watermark就闪亮登场了。源端系统产生的数据中一般都会自带当时数据发生的时间,即时间戳字段或者是时间类型字段,假如此场景中的时间字段为callTime代码就会改成这样:

streamEnv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)  //设置flink作业时间语义为EventTime
val datastream = senv.socketTextStream("localhost", 8888)
	.assignTimestampsAndWatermarks(new MyCustomerPeriodicWatermark(3000L)) 
	.map {(m: String) => (m.split(",")(0), 1) }
    .keyBy(0)
    .timeWindow(Time.seconds(10), Time.seconds(5))
    .sum(1)
    .print

class MyCustomerPeriodicWatermark(delay: Long) extends AssignerWithPeriodicWatermarks[T]{
var maxTime :Long=0
override def getCurrentWatermark: Watermark = {
new Watermark(maxTime-delay) //创建水位线
}
override def extractTimestamp(element:T,
previousElementTimestamp: Long): Long = {
maxTime=maxTime.max(element.callTime) //maxtime永远是最大值
element.callTime
}
}

在本文中,代码是次要的,相信大家仔细阅读文档,此类代码都会写。那么引入上述代码之后,跑出的结果如下:
在这里插入图片描述
一定是对的,那重点来了:为什么引入Watermark之后Flink能处理乱序或者延迟数据。我们可以先想一想,如果我们去考虑去设计,如何将乱序数据正确放入正确的窗口进行触发呢:数据延迟了,我们就延迟窗口触发嘛,等一等延迟的数据嘛,但肯定不能无限等待下去,所以还需要设置了一个延迟的程度。所以watermark的思想就是,我设置一个最大等你的时间,在窗口原本该触发的时候我不触发,我多等一会,多等我设置的最大时间,如果你在这个时间内你到了,哎,我再触发这个窗口。这个说出来好理解,那么实际上watermark如何计算呢?代码里其实已经给出:
Watermark = MaxEventTime(当前到达的最大EventTime) - delayTime(设置的延误时间)
翻译翻译,水位线其实就是一个时间戳,认定只要是小于水位线时间前的数据,我都认为它都已经来了,为什么,因为我都等了他好久了。反应的是数据到达的程度和完整性
那么窗口触发条件其实也改了,不像是Processing那样,处理时间过了10秒我就处理一个窗口,我也不管你这个窗口的数据全都到了没有,现在的触发条件为:
WaterMark > 窗口结束时间 + delayTime
翻译翻译,就是,如果我的watermark大于已经给你扩展了一段时间的窗口时间了都,那么你可以触发了,代表这个窗口的数据应该都到了。
文字总是苍白无力的,上图:在这里插入图片描述
我将m后加上时间戳,代表是那一时间生成的数据,所以上图表示的是,当数据进来的时候,m11这个点如果生成了一个watermark,那么他等于现在的最大事件时间11减去延迟时间5 等于 6对吧,它代表的是,在此时,flink认为6s前的数据已经全部到了,但此时窗口触发吗?对是不触发的,因为,此时触发条件watermark 6 不大于我给他的扩展窗口15 + 5 = 20,只有当前watermark都已经大于20,flink才能认为15秒前的数据已经都到达了,所以正是因为这种机制,虽然我的一条m6的数据延迟时(注意6s产生了两条m数据),延迟到第16s秒时,5-15s的window1还没有触发计算,因为flink还在等待有可能在15s至20s内可能延误的数据。那么是什么时候触发window1呢,继续上图:
在这里插入图片描述
为了方便理解,我们先只研究window1,那么数据如箭头一样流入flink,如图有一条m6延迟了。
到延迟的这条m6到达时,因为watermark的作用,5-15窗口还没有触发,那么现在,有一条m26的数据到了,此时,flink算出此时的watermark为26-5 = 21,发现,他已经大于我们给他的扩展窗口了,蓝色窗口为我们多等的那一部分,红色为本来的部分,此时,flink认为我21秒前的数据都应该到了,那么我触发5-15 的窗口吧~,于是window1的统计结果为(m,3)。
所以读到这里,不知道读者是否明白了Watermark的作用。
其实还是有很多问题存在的,比如如果我一些数据是延迟了7s的,一分钟的,甚至一小时的,我们没法确定具体的数据延误时间,那么在Flink应该如何处理呢?对这方面有疑问的可以找找下篇中的AllowLateness的介绍,如果没找到,那可能是还没有分享,不要走开,等等再看~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值