在使用 EventTime 处理 Stream 数据的时候会遇到数据乱序的问题,流处理从 Event(事 件)产生,流经 Source,再到 Operator,这中间需要一定的时间。虽然大部分情况下,传输到 Operator 的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络延迟等原因而导致乱序的产生,特别是使用 Kafka 的时候,多个分区之间的数据无法保证有序。因此, 在进行 Window 计算的时候,不能无限期地等下去,必须要有个机制来保证在特定的时间后, 必须触发 Window 进行计算,这个特别的机制就是 Watermark(水位线)。Watermark 是用于 处理乱序事件的。
在 Flink 的窗口处理过程中,如果确定全部数据到达,就可以对 Window 的所有数据做 窗口计算操作(如汇总、分组等),如果数据没有全部到达,则继续等待该窗口中的数据全 部到达才开始处理。这种情况下就需要用到水位线(WaterMarks)机制,它能够衡量数据处 理进度(表达数据到达的完整性),保证事件数据(全部)到达 Flink 系统,或者在乱序及 延迟到达时,也能够像预期一样计算出正确并且连续的结果。当任何 Event 进入到 Flink 系统时,会根据当前最大事件时间产生 Watermarks 时间戳。
如何计算WaterMark的值?
Watermark = 进入 Flink 的最大的事件时间(mxtEventTime)— 指定的延迟时间(t)
有Watermark 的 Window 是怎么触发窗口函数?
如果有窗口的停止时间等于或者小于maxEventTime – t(当时的 warkmark),那么 这个窗口被触发执行
Watermark 的使用存在三种情况:
如果数据元素的事件时间是有序的,Watermark 时间戳会随着数据元素的事件时间按顺 序生成,此时水位线的变化和事件时间保持一直(因为既然是有序的时间,就不需要设置延 迟了,那么 t 就是 0。所以 watermark=maxtime-0 = maxtime),也就是理想状态下的水位 线。当 Watermark 时间大于 Windows 结束时间就会触发对 Windows 的数据计算,以此类推,下一个 Window 也是一样。
现实情况下数据元素往往并不是按照其产生顺序接入到 Flink 系统中进行处理,而频繁出现乱序或迟到的情况,这种情况就需要使用 Watermarks 来应对。比如下图,设置延迟时 间t为2
在多并行度的情况下,Watermark 会有一个对齐机制,这个对齐机制会取所有 Channel中最小的 Watermark。
object WaterMark1 { def main(args: Array[String]): Unit = { val env = StreamExecutionEnvironment.getExecutionEnvironment // 使用eventTime env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) val stream= env.socketTextStream("flink101", 8888) .map(line => { var arr = line.split(",") Log(arr(0).trim,arr(1).trim, arr(2).trim, arr(3).trim, arr(4).trim.toLong, arr(5).trim.toLong) }).assignAscendingTimestamps(_.callTime) // 数据有序的升序watermark .filter(_.callType.equals("success")) .keyBy(_.sid) .timeWindow(Time.seconds(10), Time.seconds(5)) .reduce(new MyReduceFunction(), new ReturnMaxTimeWindowFunction) env.execute("assignAscendingTimestampsDemo") }
object Watermark2 { def main(args: Array[String]): Unit = { val env = StreamExecutionEnvironment.getExecutionEnvironment // 使用eventTime env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) // 周期引入watermart的设置,默认是100毫秒 env.getConfig.setAutoWatermarkInterval(100L) val stream= env.socketTextStream("flink101", 8888) .map(line => { var arr = line.split(",") Log(arr(0).trim,arr(1).trim, arr(2).trim, arr(3).trim, arr(4).trim.toLong, arr(5).trim.toLong) }) // 数据是乱序的,延迟时间为3秒,周期性watermark /** * 第一种实现 */ val ds = stream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[Log](Time.seconds(3)){ override def extractTimestamp(element: Log): Long = { element.callTime // EventTime } }) /** * 第二种实现 */ val ds2 = stream.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks[Log] { var maxEventTime: Long =_ override def getCurrentWatermark: Watermark = { // 周期性生成watermark new Watermark(maxEventTime - 3000L) } // 设定EventTime是哪个属性 override def extractTimestamp(element: Log, previousElementTimestamp: Long): Long = { maxEventTime = maxEventTime.max(element.callTime) element.callTime } }) env.execute("assignTimestampsAndWatermarksDemo") }
val env = StreamExecutionEnvironment.getExecutionEnvironment // 使用eventTime env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) //读取文件数据 val data = env.socketTextStream("flink101",8888) .map(line=>{ var arr =line.split(",") new StationLog(arr(0).trim,arr(1).trim,arr(2).trim,arr(3).trim,arr(4).trim.toLong,arr(5).trim.to Long) }) // 生成watermark data.assignTimestampsAndWatermarks( new MyCustomerPunctuatedWatermarks(3000L)) //自定义延迟 } class MyCustomerPunctuatedWatermarks(delay:Long) extends AssignerWithPunctuatedWatermarks[StationLog]{ var maxTime :Long=0 override def checkAndGetNextWatermark(element: StationLog, extractedTimestamp: Long): Watermark = { if(element.sid.equals("station_1")){//当ID为:station_1 才生成水位线 maxTime =maxTime.max(extractedTimestamp) new Watermark(maxTime-delay) }else{ return null //其他情况下不返回水位线 } } override def extractTimestamp(element: StationLog, previousElementTimestamp: Long): Long = { element.callTime //抽取EventTime的值 } }
以上三种Watermark的实现,根据数据的事件时间是否有延迟和业务需求选择相应的生成WaterMark的方法。
原文:https://www.cnblogs.com/zfwwdz/p/13159158.html