window对数据的操作
flink在对流数据进行窗口操作时,当eventtime在过了窗口时间后会进行关窗操作。然后在实际生产环境中,由于网络等各种各样的原因可能会出现迟到数据,导致数据乱序。如下图所示,圆圈里的数字代表时间,这时候如果对数据进行一个5秒的滚动窗口操作,当第6秒的数据进入时,系统会讲0-5秒的窗口关闭,这时候4秒的数据就会丢失。
遇到这种情况除了调用allowedLateness和侧输出流外,还可以使用watermark。watermark的使用可以处理乱序数据,通过watermark来定义关窗的时间点。
什么是watermark
从watermark字面意思可以理解成水位线,水位线在现实生活中可以理解成表示水位高低的刻度,在flink的语义中watermark也可以理解成一个时间刻度。举一个简单一点的例子,某个航线每天早上10点为首班,每隔半个小时有一班航班起飞,每个起飞点都可以理解成watermark,乘客可以理解成一个个事件。如果所有乘客在起飞之前到达则可以正常乘坐飞机到达目的地,就是有序事件的水位线,如下图所示,1234567都到达了自己的水位线。如果有乘客迟到,飞机不会等他直接飞走,这种情况就比较像乱序事件的水位线,如下图的45一样,如果不对45进行处理,这些数据则会丢失。
有序时间水位线
乱序时间水位线
watermark测试
package com.stanley.flink
/**
* 一个订单样例类
* @param timestamp 事件事件
* @param category 商品类别
* @param price 价格
*/
case class Order(timestamp:Long,category:String,price:Double)
package com.stanley.flink
import org.apache.flink.api.java.tuple.{Tuple, Tuple1}
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.WindowFunction
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector
object WatermarkTest {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
//设置为eventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//并行度设置为1
env.setParallelism(1)
//创建文本流
val inputStream:DataStream[String] = env.socketTextStream("node1",9999)
//转换成order样例类
val dataStream:DataStream[Order] = inputStream.map(str=>{
val order = str.split(",")
new Order(order(0).toLong,order(1),order(2).toDouble)})
val outputStream:DataStream[Order] = dataStream.
//调用watermark,最大乱序事件设置为0
assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[Order](Time.seconds(0)) {
override def extractTimestamp(t: Order): Long = t.timestamp*1000L
}).keyBy("category").timeWindow(Time.seconds(5)).apply(new MyWindowFunction)
dataStream.print("Data")
outputStream.print("Result")
env.execute()
}
}
//自定义Windowfunction,对类别进行聚合,返回时间戳为窗口的最大事件
class MyWindowFunction() extends WindowFunction[Order,Order,Tuple,TimeWindow]{
override def apply(key: Tuple, window: TimeWindow, input: Iterable[Order], out: Collector[Order]): Unit = {
val timestamp = window.maxTimestamp()
var sum:Double = 0
for (elem <- input) {
sum = elem.price+sum
}
//java Tuple转换
val category:String = key.asInstanceOf[Tuple1[String]].f0
out.collect(new Order(timestamp,category,sum))
}
}
输入以下四条数据,当事件事件为390时窗口关闭,对385-389这个时间戳内的事件进行分类聚合,在控制台输出,390的事件为下一个窗口的事件。此时的watermark为390,再次输入390之前的事件,这些数据如果不做处理将丢失
只对390的food进行聚合,389的数据丢失
如何通过watermark处理迟到数据
watermark的函数可以通过传入有一个时间来延迟watermark到来的时间,从源码上就能看到,watermark的是当前的事件时间减去传入的最大乱序时间
修改最大乱序时间,延迟watermark,
再次输入同样的数据,此时控制台没有打印result数据,因为设置了最大乱序时间为1秒,此时的watermark为389
当事件时间变为391时,390的watermark到来,385-389的时间窗口关闭
多任务并行,下游的watermark怎么定义
当上游的多个任务并行,下游的watermark以上游最小的watermark为准
将代码的并行度设置为2
再次输入同样数据,slot2的watermark已经到达390而slot1的watermark还是389,所以下游的watermark还是389,所以没有数据输出
当再次输入392的事件,此时事件轮询到slot1,slot1的watermark变为391,slot2的watermark还是390,下游的watermark为390导致窗口关闭,输入结果数据