Flink(八)时间语义与 Wartermark

一、Flink中的时间语义

 

       在 Flink 的流式处理中,会涉及到时间的不同概念,如下图所示:

Event Time

是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条日志都会记录自己的生成时间Flink 通过时间戳分配器访问事件时间戳
Ingestion time 是数据进入 Flink 的时间
Processing Time 是每一个执行基于时间操作的算子的本地系统时间,与机器相关,默认的时间属性就是 Processing Time

 二、EventTime的引入

Flink 的流式处理中,绝大部分的业务都会使用 eventTime,一般只在eventTime 无法使用时,才会被迫使用 ProcessingTime 或者 IngestionTime。如果要使用 EventTime,那么需要引入 EventTime 的时间属性,引入方式如下所示:

val env = StreamExecutionEnvironment.getExecutionEnvironment

// 从调用时刻开始给 env 创建的每一个 stream 追加时间特征

env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

三、Watermark

1.基本概念

我们知道,流处理从事件产生,到流经 source,再到 operator,中间是有一个过程和时间的,虽然大部分情况下,流到 operator 的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络、分布式等原因,导致乱序的产生,所谓乱序,就是指 Flink 接收到的事件的先后顺序不是严格按照事件的 Event Time 顺序排列的。

那么此时出现一个问题,一旦出现乱序,如果只根据 eventTime 决定 window 的运行,我们不能明确数据是否全部到位,但又不能无限期的等下去,此时必须要有个机制来保证一个特定的时间后,必须触发 window 去进行计算了,这个特别的机制,就是 Watermark。

Watermark 是一种衡量 Event Time 进展的机制。

Watermark 是用于处理乱序事件的 ,而正确的处理乱序事件,通常用Watermark 机制结合 window 来实现。

数据流中的 Watermark 用于表示 timestamp 小于 Watermark 的数据,都已经到达了,因此,window 的执行也是由 Watermark 触发的。

Watermark 可以理解成一个延迟触发机制,我们可以设置 Watermark 的延时时长 t,每次系统会校验已经到达的数据中最大的 maxEventTime,然后认定 eventTime小于 maxEventTime - t 的所有数据都已经到达,如果有窗口的停止时间等于 maxEventTime – t,那么这个窗口被触发执行。

 2.Watermark 的引入

    watermark 的引入很简单,对于乱序数据,最常见的引用方式如下:

dataStream.assignTimestampsAndWatermarks( new

BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.millisecond

s(1000)) {

override def extractTimestamp(element: SensorReading): Long = {

element.timestamp * 1000

}

} )

 

我们看到上面的例子中创建了一个看起来有点复杂的类,这个类实现的其实就是分配时间戳的接口。Flink 暴露了 TimestampAssigner 接口供我们实现,使我们可以自定义如何从事件数据中抽取时间戳。 

val env = StreamExecutionEnvironment.getExecutionEnvironment

// 从调用时刻开始给 env 创建的每一个 stream 追加时间特性 env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

val readings: DataStream[SensorReading] = env

.addSource(new SensorSource)

.assignTimestampsAndWatermarks(new MyAssigner())

MyAssigner 有两种类型,这两个接口都继承自 TimestampAssigner。

  • AssignerWithPeriodicWatermarks
  • AssignerWithPunctuatedWatermarks

3.Assigner with periodic watermarks

周期性的生成 watermark:系统会周期性的将 watermark 插入到流中(水位线也是一种特殊的事件!)。默认周期是 200 毫秒。可以使用ExecutionConfig.setAutoWatermarkInterval() 方法进行设置。

val env = StreamExecutionEnvironment.getExecutionEnvironment env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
// 每隔 5 秒产生一个 watermark 
env.getConfig.setAutoWatermarkInterval(5000)

例子,自定义一个周期性的时间戳抽取:

class PeriodicAssigner extends

AssignerWithPeriodicWatermarks[SensorReading] { val bound: Long = 60 * 1000 // 延时为 1 分钟

var maxTs: Long = Long.MinValue // 观察到的最大时间戳
override def getCurrentWatermark: Watermark = { new Watermark(maxTs - bound)
}
override def extractTimestamp(r: SensorReading, previousTS: Long) = { maxTs = maxTs.max(r.timestamp)
r.timestamp

}

}

而对于乱序数据流,如果我们能大致估算出数据流中的事件的最大延迟时间,就可以使用如下代码:

val stream: DataStream[SensorReading] = ...

val withTimestampsAndWatermarks = stream.assignTimestampsAndWatermarks(
new SensorTimeAssigner

)

class SensorTimeAssigner extends

BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(5)) {

// 抽取时间戳

override def extractTimestamp(r: SensorReading): Long = r.timestamp

}

 4.Assigner with punctuated watermarks

间断式地生成 watermark。和周期性生成的方式不同,这种方式不是固定时间的,而是可以根据需要对每条数据进行筛选和处理。直接上代码来举个例子,我们只给 sensor_1 的传感器的数据流插入 watermark:

class PunctuatedAssigner extends

AssignerWithPunctuatedWatermarks[SensorReading] {

val bound: Long = 60 * 1000
override def checkAndGetNextWatermark(r: SensorReading, extractedTS:

Long): Watermark = {

if (r.id == "sensor_1") {

new Watermark(extractedTS - bound)

} else { null

}

}

override def extractTimestamp(r: SensorReading, previousTS: Long): Long

= {

r.timestamp

}
}

四、EvnetTime 在 window 中的使用

1.滚动窗口

def main(args: Array[String]): Unit = {

//环境

val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment


env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) env.setParallelism(1)


val dstream: DataStream[String] = env.socketTextStream("localhost",7777)


val textWithTsDstream: DataStream[(String, Long, Int)] = dstream.map

{ text =>

val arr: Array[String] = text.split(" ") (arr(0), arr(1).toLong, 1)

}

val textWithEventTimeDstream: DataStream[(String, Long, Int)] = textWithTsDstream.assignTimestampsAndWatermarks(new

BoundedOutOfOrdernessTimestampExtractor[(String, Long,

Int)](Time.milliseconds(1000)) {

override def extractTimestamp(element: (String, Long, Int)): Long = {

return	element._2

}

})

val textKeyStream: KeyedStream[(String, Long, Int), Tuple] = textWithEventTimeDstream.keyBy(0)

textKeyStream.print("textkey:")

val windowStream: WindowedStream[(String, Long, Int), Tuple, TimeWindow] = textKeyStream.window(TumblingEventTimeWindows.of(Time.seconds(2)))


val groupDstream: DataStream[mutable.HashSet[Long]] = windowStream.fold(new mutable.HashSet[Long]()) { case (set, (key, ts, count))

=>

set += ts

}
groupDstream.print("window::::").setParallelism(1)
env.execute()

}

}

2.滑动窗口

def main(args: Array[String]): Unit = {

//环境

val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment


env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) env.setParallelism(1)


val dstream: DataStream[String] = env.socketTextStream("localhost",7777)



val textWithTsDstream: DataStream[(String, Long, Int)] = dstream.map { text

=>

val arr: Array[String] = text.split(" ") (arr(0), arr(1).toLong, 1)

}

val textWithEventTimeDstream: DataStream[(String, Long, Int)] = textWithTsDstream.assignTimestampsAndWatermarks(new

BoundedOutOfOrdernessTimestampExtractor[(String, Long,

Int)](Time.milliseconds(1000)) {

override def extractTimestamp(element: (String, Long, Int)): Long = {

return	element._2

}

})

val textKeyStream: KeyedStream[(String, Long, Int), Tuple] = textWithEventTimeDstream.keyBy(0)

textKeyStream.print("textkey:")

val windowStream: WindowedStream[(String, Long, Int), Tuple, TimeWindow] = textKeyStream.window(SlidingEventTimeWindows.of(Time.seconds(2),Time.millis econds(500)))


val groupDstream: DataStream[mutable.HashSet[Long]] = windowStream.fold(new mutable.HashSet[Long]()) { case (set, (key, ts, count)) =>

set += ts

}

groupDstream.print("window::::").setParallelism(1)

env.execute()

}

3.会话窗口

def main(args: Array[String]): Unit = {

//环境

val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment


env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) env.setParallelism(1)


val dstream: DataStream[String] = env.socketTextStream("localhost",7777)



val textWithTsDstream: DataStream[(String, Long, Int)] = dstream.map { text

=>

val arr: Array[String] = text.split(" ") (arr(0), arr(1).toLong, 1)

}

val textWithEventTimeDstream: DataStream[(String, Long, Int)] = textWithTsDstream.assignTimestampsAndWatermarks(new

BoundedOutOfOrdernessTimestampExtractor[(String, Long,

Int)](Time.milliseconds(1000)) {

override def extractTimestamp(element: (String, Long, Int)): Long = {

return	element._2

}

})

val textKeyStream: KeyedStream[(String, Long, Int), Tuple] = textWithEventTimeDstream.keyBy(0)

textKeyStream.print("textkey:")

val windowStream: WindowedStream[(String, Long, Int), Tuple, TimeWindow]

=

textKeyStream.window(EventTimeSessionWindows.withGap(Time.milliseconds(500)

) )

windowStream.reduce((text1,text2)=> ( text1._1,0L,text1._3+text2._3)

)	.map(_._3).print("windows:::").setParallelism(1)
env.execute()

}

猜你喜欢

转载自blog.csdn.net/weixin_43233971/article/details/107457360