Flink Time的操作
时间语义
针对stream流中的事件,分为三种:
1.Event Time:事件本身的时间
2.Ingestion Time:进入系统的时间
3.Process Time:处理消息的时间


哪种时间语义更重要:
1.不同的时间语义有不同的应用场合
2.在Flink处理数据时,更关心事件时间,因为绝大部分业务都会使用event time,一般只在event time无法使用的时候,才会使用process time或者Ingestion time。
3.使用event time的时间属性,引入的方式如下图(python)

时间乱序数据的影响:
从事件产生,流经source,再到operator,中间是有一个过程和时间的,当Flink以event time处理数据时,它会根据事件本身自带的时间戳处理数据。但是,不排除可能会因为网络延迟、分布式原因或者其它什么原因导致时间乱序的现象。
时间乱序是指Flink接收到事件的顺序不是按照发生事件event time顺序排序来的。
举例:
有一个时间窗口
开始时间:2019-03-03 10:00:00
结束时间:2018-03-03 10:10:00
有一个数据,因为网络延迟
事件发生的时间是:2019-03-03 10:10:00
但进入到窗口的时间为:2019-03-03 10:10:02,延迟了2秒钟
时间窗口并没有将10:00:00的这个数据计算进来,导致数据计算不准确
使用水印解决时间乱序的问题
水印(watermark)就是一个时间戳,Flink可以给流数据添加水印,就是收到消息后,额外给这个事件加一个时间的字段。
1.水印并不会影响原有的Event time
2.当数据添加水印后,会按照水印的时间来触发窗口的计算
3.一般设置水印时间会比event time小几秒钟
4.当接收到的水印时间>=窗口的end time,则触发计算
示例:
编写代码,计算5秒内,用户的订单总额
订单数据(订单ID-UUID,用户ID,时间戳,订单金额),用户的订单总额
步骤
1.创建流处理运行环境
2.设置处理时间Event Time
3.创建一个订单样例类Order,包含四个字段(订单ID、用户ID、订单金额、时间戳)
4.创建一个自定义数据源
(1)随机生成订单ID(UUID)
(2)随机生成用户ID(0-2)
(3)随机生成订单金额(0-100)
(4)时间戳为当前系统时间
(5)每隔1S生成一个订单
5.添加水印
(1)允许延迟2秒
(2)在获取水印方法中,打印水印时间、事件时间和当前系统时间
6.按照用户进行分流
7.设置5秒时间窗口
8.进行聚合计算
9.打印结果数据
10.启动执行流处理
package com.boncscla
import java.util.concurrent.TimeUnit
import java.util.{Date, UUID}
import org.apache.flink.api.scala._
import org.apache.commons.lang.time.FastDateFormat
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks
import org.apache.flink.streaming.api.functions.source.{RichSourceFunction, SourceFunction}
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.time.Time
import scala.util.Random
object WatemarkDemo {
// 3.创建一个样例类
case class Order(orderId:String, userId:Int, money:Long, timestamp: Long)
def main(args: Array[String]): Unit = {
// 1.创建流式运行环境
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
// 2.设置处理时间为Event time
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
// 4.创建一个自定义数据源
val orderDataStream: DataStream[Order] = env.addSource(new RichSourceFunction[Order] {
var isRunning = true
override def run(ctx: SourceFunction.SourceContext[Order]): Unit = {
while (isRunning) {
// 随机生成订单id
// 随机生成用户id
// 随机生成订单金额
// 时间戳为当前系统时间
// 每隔1S生成一个订单
val order = Order(UUID.randomUUID().toString, Random.nextInt(3), Random.nextInt(101), new Date().getTime)
ctx.collect(order)
TimeUnit.SECONDS.sleep(1)
}
}
override def cancel(): Unit = isRunning = false
})
// 5.添加水印
val watermarkDataStream: DataStream[Order] = orderDataStream.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks[Order] {
var currentTimestamp = 0L
val delayTime = 2000
override def getCurrentWatermark: Watermark = {
// 允许延迟2秒
// 获取水印的方中,打印水印时间、当前事件时间和当前系统时间
val watermark = new Watermark(currentTimestamp - delayTime)
val dateFormat = FastDateFormat.getInstance("HH:mm:ss")
println(s"当前水印时间${dateFormat.format(watermark.getTimestamp)}, 当前事件时间:${dateFormat.format(currentTimestamp)}," +
s"当前系统时间:${dateFormat.format(System.currentTimeMillis())}")
watermark
}
override def extractTimestamp(element: Order, previousElementTimestamp: Long): Long = {
val timestamp = element.timestamp
currentTimestamp = Math.max(currentTimestamp, timestamp)
currentTimestamp
}
})
// 6.按照用户进行分流
// 7.设置5S的时间窗口
// 8.进行聚合计算
// 9.打印结果数据
// 10.启动执行流处理
watermarkDataStream.keyBy(_.userId)
.timeWindow(Time.seconds(5))
.reduce{
(order1, order2) =>
Order(order2.orderId, order2.userId, order1.money + order2.money, 0)
}.print()
env.execute()
}
}
还可以使用BoundedOutOfOrdernessTimestampExtractor的方式去实现,代码如下
package com.boncscla
import java.sql.Timestamp
import java.util.concurrent.TimeUnit
import java.util.{Date, UUID}
import org.apache.commons.lang.time.FastDateFormat
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.source.{RichSourceFunction, SourceFunction}
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.api.scala._
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.windowing.time.Time
import scala.util.Random
object WatermarkDemo2 {
// 3.创建一个订单样例类
case class Order(orderId:String, userId:Int, money:Long, timestamp: Long)
def main(args: Array[String]): Unit = {
// 1.创建流处理运行环境
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
// 2.设置处理时间为Event time
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
// 4.创建一个自定义数据源
val orderDataStream: DataStream[Order] = env.addSource(new RichSourceFunction[Order] {
var isRunning = true
override def run(ctx: SourceFunction.SourceContext[Order]): Unit = {
while (isRunning) {
// 随机生成订单id(UUID)
// 随机生成用户id(0-2)
// 随机生成订单金额
// 时间戳为当前系统时间
// 每隔1s生成一个订单
val order = Order(UUID.randomUUID().toString, Random.nextInt(3), Random.nextInt(101), new Date().getTime)
ctx.collect(order)
TimeUnit.SECONDS.sleep(1)
}
}
override def cancel(): Unit = isRunning = false
})
// 5.添加水印
val watermarkDataStream: DataStream[Order] = orderDataStream.assignTimestampsAndWatermarks(
new BoundedOutOfOrdernessTimestampExtractor[Order](Time.seconds(2)) {
override def extractTimestamp(element: Order): Long = {
val dateFormat = FastDateFormat.getInstance("HH:mm:ss")
println(s"当前水印时间:${dateFormat.format(getCurrentWatermark.getTimestamp)}," + s"当前事件时间:${dateFormat.format(element.timestamp)}" +
s"当前系统时间:${dateFormat.format(System.currentTimeMillis())}")
element.timestamp
}
})
// 6.按照用户分流
// 7.设置5S的时间窗口
// 8.进行聚合计算
// 9.打印结果数据
// 10.启动执行力处理
watermarkDataStream
.keyBy(_.userId)
.timeWindow(Time.seconds(5))
.reduce {
(order1, order2) => Order(order2.orderId, order2.userId, order1.money + order2.money, 0)
}.print()
env.execute()
}
}
延迟数据的处理
对于延迟太多的数据,Flink处理方案有三方式:
方案1(默认):丢弃
方案2:allowedLateness指定允许数据延迟的时间
方案3:sideOutputLateData收集迟到的数据
1.方案1就不多说了,处理改时间段的窗口已经计算结束,延迟数据丢弃
2.方案2:指定允许数据延迟的时间(allowedLateness)
触发执行的时机
第一次触发是在watermark>=window_end_time时
第二次(或多次)触发的条件是watermark<window_end_time + allowedLateness时间内,这个窗口有late数据到达时
代码启用数据延迟触发机制
.timewindow(Time.seconds(3))
.allowedLateness(Time.seconds(2))
.apply(new RichWindowFunction[(String,Long),String,Tuple,TimeWindow])
因为watermark的时间戳也是递增的,所以迟到太久的数据,还是会丢弃。
3.方案3:sideOutputLateData收集迟到的数据
通过sideOutputData可以把迟到的数据统一收集,统一存储
代码启用侧输出流机制
...
val outputTag = new OutputTag[(String,Long)]("late_data")
...
.timewindow(Time.seconds(3))
.sideOutputData(outputTag)
.apply(new RichWindowFunction[(String,Long),String,Tuple,TimeWindow]){
...
ws.getSideOutput(outputTag)
.print("侧输出流中延迟的数据")
ws.print("正常窗口中的数据")
}
总结
1.在接收到source的数据后,然后立刻生成watermark,也可以在source后,应用简单的map或者filter操作后,在生成watermark,如果指定多次watermark,后面会覆盖前面的值。
2.Flink应该如何设置最大乱序时间
(1)这个要结合自己的业务及数据情况去设置
(2)如果maxOutofOrderness设置的太小,而自身数据发送时由于网络等原因导致乱序或者late太多,那么最终的结果就是会有很多单条的数据在window中被触发。
(3)对于严重乱序的数据,需要严格统计数据最大延迟时间,才能保证数据的计算准确
(4)延迟性设置太小影响数据准确性,延迟设置太久不仅会影响实时性,而且会给flink作业增加负担,不是对eventTime要求特别严格的数据,尽量不要采用eventTime处理数据,会有丢数据的风险。

987

被折叠的 条评论
为什么被折叠?



