Flink Time

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处理数据,会有丢数据的风险。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值