• Flink WaterMark原理与实现


    一、WaterMark作用

    在使用 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 的使用存在三种情况:

    1. 本来有序的 Stream 中的 Watermark

    如果数据元素的事件时间是有序的,Watermark 时间戳会随着数据元素的事件时间按顺 序生成,此时水位线的变化和事件时间保持一直(因为既然是有序的时间,就不需要设置延 迟了,那么 t 就是 0。所以 watermark=maxtime-0 = maxtime),也就是理想状态下的水位 线。当 Watermark 时间大于 Windows 结束时间就会触发对 Windows 的数据计算,以此类推,下一个 Window 也是一样。

    2.乱序事件中的 Watermark

    现实情况下数据元素往往并不是按照其产生顺序接入到 Flink 系统中进行处理,而频繁出现乱序或迟到的情况,这种情况就需要使用 Watermarks 来应对。比如下图,设置延迟时 间t为2

     3.并行数据流中的 Watermark

     在多并行度的情况下,Watermark 会有一个对齐机制,这个对齐机制会取所有 Channel中最小的 Watermark。

    三、Watermark 运用

    1.有序的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")
      }
    

    2.无序的Watermark

    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")
      }
    

     

    3.With Punctuated(间断性的) Watermark

    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的方法。

  • 相关阅读:
    内置函数
    json && pickle 反序列化
    装饰器(语法糖)
    shell环境改变引起的命令提示符改变
    FTP服务安装与端口说明
    tomcat配置
    greenDao 中连接查询
    COM组件DLL引用时出现检索组件错误
    VS 调试断点命中了,程序无法再断点处中断
    单点登录原理与简单实现
  • 原文地址:https://www.cnblogs.com/zfwwdz/p/13159158.html
Copyright © 2020-2023  润新知