Flink--WaterMark原理和实现

本文详细介绍了Flink中Watermark的工作原理及其应用场景。探讨了在处理乱序事件时如何通过Watermark确保窗口计算的准确性和及时性,并提供了有序与无序场景下的具体实现示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

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的值 }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值