1. 时间语义

flinkcdc指定timestamp flink timecharacteristic_flinkcdc指定timestamp

  • 事件时间 业务发生时的时间。
  • 获取时间 flink中DataSource拿到数据的时间。
  • 处理时间 flink开始处理业务的时间。
//指定时间语义
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

1.12版本之前,默认的时间语义是处理时间(ProcessingTime)
1.12版本之后,默认的时间语义是事件时间(EventTime)

flinkcdc指定timestamp flink timecharacteristic_数据_02

2. 水位线

2.1 事件时间与窗口

应用场景:每隔5分钟统计一次页面访问次数(PV)

用户

事件

事件时间

页面

停留时长(分钟)

user1

view

2022-05-22 08:01

page1

3

user2

view

2022-05-22 08:03

page1

1

user3

view

2022-05-22 08:05

page1

1

user4

view

2022-05-22 08:06

page1

1

基于事件时间的业务顺序数据(理想)

flinkcdc指定timestamp flink timecharacteristic_大数据_03

基于事件时间的乱序情况下的数据(实际)

flinkcdc指定timestamp flink timecharacteristic_大数据_04

时间乱序问题

2.2 什么是水位线

理论上“先产生的数据先被处理”,这要求我们需要保证数据到达的顺序。但是由于分布式系统中网络传输延迟的不确定性,实际应用中我们要面对的数据流往往是乱序的。在这种情况下,就不能简单地把数据自带的时间戳当作时钟了,而需要用另外的标志来表示事件时间进展,在 Flink 中把它叫作事件时间的“水位线”(Watermarks)

flinkcdc指定timestamp flink timecharacteristic_flink_05

2.2.1 有序流中的水位线

逐个式生成水位线

flinkcdc指定timestamp flink timecharacteristic_大数据_06

周期性生成水位线

flinkcdc指定timestamp flink timecharacteristic_flinkcdc指定timestamp_07

2.2.2 乱序流中的水位线

逐个式生成水位线

flinkcdc指定timestamp flink timecharacteristic_数据_08

保证水位线的时间标记是单调递增
如果遇到迟到的数据,使用最大的时间戳。

周期性生成水位线

区间:[0,9)

flinkcdc指定timestamp flink timecharacteristic_flink_09

flinkcdc指定timestamp flink timecharacteristic_数据_10

2.2.3 乱序流产生的问题

水位线的目的:告知程序当前的时间,以便触发窗口的关闭操作。当水位线的时间大于窗口的关闭时间时,即触发窗口的关闭操作。
产生的问题: 迟到的数据未统计进来。

为了解决了这个问题,增加了一种规则,允许将水位线的时间调慢。

乱序流中的水位线时间调慢2秒

flinkcdc指定timestamp flink timecharacteristic_scala_11

这里只是调慢了窗口关闭的时间,窗口的时间区间不变。

窗口区间: [8:00, 8:05) [8:05, 8:10)

flinkcdc指定timestamp flink timecharacteristic_scala_12

水位线的特征总结:

  • 水位线是插入到数据流中的一个标记,可以认为是一种特殊的数据
  • 水位线的主要内容是一个时间戳,用来表示当前事件时间的进展
  • 水位线是基于数据的时间戳生成
  • 水位线的时间戳必须单调递增,以确保任务的事件时间一直向前推进
  • 水位线可以通过设置延迟,来保证正确的处理乱序数据
  • 一个水位线,表示在当前流中事件时间已经达到了时间戳t,这代表t之前的所有数据都到齐了,之后流中不会出现时间戳<=t的数据。

水位线是Flink流处理中保证结果正确的核心机制,它往往会跟窗口一起配合,完成对乱序数据的正确处理。

2.3 如何生成水位线

2.3.1 水位线生成原则

水位线一旦生成,就表示这个时间之前的数据已经全部到齐,之后再也不会出现了。如果后面出现,就会出现丢弃的情况。

如何设置水位线的延迟时间

  • 个人经验
  • 根据历史的迟到数据的延迟时间估算

2.3.2 水位线生成策略

在程序中实现方式:

val ds = env.readTextFile(file)
        .map{x=>
          val arr = x.split(",")
          UserBehavior(arr(0),arr(1),arr(2),arr(3),arr(4).toInt)
        }
      .assignTimestampsAndWatermarks(  
      WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofMinutes(0)) //指定水位线策略
      .withTimestampAssigner(new SerializableTimestampAssigner[UserBehavior] {
      override def extractTimestamp(element: UserBehavior, recordTimestamp: Long): Long = {
        DateUtil.getTimestamp(element.dt) //时间戳
      }

内置水位线生成策略:
(1)有序流 forMonotonousTimestamps

static <T> WatermarkStrategy<T> forMonotonousTimestamps() {
        return (ctx) -> new AscendingTimestampsWatermarks<>();
    }
@Public
public class AscendingTimestampsWatermarks<T> extends BoundedOutOfOrdernessWatermarks<T> {

    /** Creates a new watermark generator with for ascending timestamps. */
    public AscendingTimestampsWatermarks() {
        super(Duration.ofMillis(0));
    }
}

(2)乱序流 forBoundedOutOfOrderness
参数: 延迟时间

static <T> WatermarkStrategy<T> forBoundedOutOfOrderness(Duration maxOutOfOrderness) {
        return (ctx) -> new BoundedOutOfOrdernessWatermarks<>(maxOutOfOrderness);
    }

源码:

@Public
public class BoundedOutOfOrdernessWatermarks<T> implements WatermarkGenerator<T> {

    private long maxTimestamp;
    private final long outOfOrdernessMillis;

    public BoundedOutOfOrdernessWatermarks(Duration maxOutOfOrderness) {
        checkNotNull(maxOutOfOrderness, "maxOutOfOrderness");
        checkArgument(!maxOutOfOrderness.isNegative(), "maxOutOfOrderness cannot be negative");

        this.outOfOrdernessMillis = maxOutOfOrderness.toMillis();
        // start so that our lowest watermark would be Long.MIN_VALUE.
        this.maxTimestamp = Long.MIN_VALUE + outOfOrdernessMillis + 1;
    }

    @Override
    public void onEvent(T event, long eventTimestamp, WatermarkOutput output) {
        maxTimestamp = Math.max(maxTimestamp, eventTimestamp);
    }

    @Override
    public void onPeriodicEmit(WatermarkOutput output) {
        output.emitWatermark(new Watermark(maxTimestamp - outOfOrdernessMillis - 1));
    }
}

2.3 3 水位线生成频率

默认200ms

//设置周期性watermark生成的间隔
    env.getConfig.setAutoWatermarkInterval(500)

2.3.4 自定义水位线生成策略

.assignTimestampsAndWatermarks(
        WatermarkStrategy.forGenerator[UserBehavior](context => new MyWatermarkWatermarkCreate())
        .withTimestampAssigner(new SerializableTimestampAssigner[UserBehavior](){
          override def extractTimestamp(element: UserBehavior, recordTimestamp: Long): Long = {
            DateUtil.getTimestamp(element.dt)
          }
        })
      )

(1)自定义周期性的生成水位线:

class MyWatermarkWatermarkCreate extends WatermarkGenerator[UserBehavior]{

    val delayTime = 5000L  //延迟时间
    var maxTime = Long.MinValue + delayTime + 1L  //最大时间戳

    override def onEvent(event: UserBehavior, eventTimestamp: Long, output: WatermarkOutput): Unit = {
      //每来一个事件,更新下最大的时间戳
      maxTime = DateUtil.getTimestamp(event.dt).max(maxTime)
    }

    override def onPeriodicEmit(output: WatermarkOutput): Unit = {
      //发射水位线,默认200ms一次
      val time = maxTime - delayTime - 1L
      println("send watermark==>" +  time +" "+ DateUtil.getDateTime(time.toString))
      output.emitWatermark(new Watermark(time))
    }
  }

flinkcdc指定timestamp flink timecharacteristic_scala_13

(2)自定义断点式生成水位线

class MyWatermarkWatermarkCreate2 extends WatermarkGenerator[UserBehavior]{
    override def onEvent(event: UserBehavior, eventTimestamp: Long, output: WatermarkOutput): Unit = {
      val time = eventTimestamp - 5000L - 1L
      println("send watermark==>" +  time +" "+ DateUtil.getDateTime(time.toString))
      new Watermark(time)
//      if(event.event.equals("view")){  //当某个事件等于'view'时才生成水位线
//        new Watermark(time)
//      }
    }

    override def onPeriodicEmit(output: WatermarkOutput): Unit = {
      //
    }
  }

flinkcdc指定timestamp flink timecharacteristic_flinkcdc指定timestamp_14

2.4 水位线的传递

flinkcdc指定timestamp flink timecharacteristic_scala_15


flinkcdc指定timestamp flink timecharacteristic_scala_16


每个任务中都会维护上游的任务传递过来的水位线的状态。

当前任务的水位线 取决于上游任务的水位线状态中最小的值。