Flink04 --时间语义和 watermark

本文介绍了Flink中的窗口概念,包括时间窗口、滑动窗口、会话窗口和计数窗口。详细讲解了新建窗口及聚合方式,如增量聚合和全窗口函数。此外,文章还探讨了Flink的时间语义,强调了Event Time的重要性,并详细阐述了Watermark的原理和作用,以及如何解决延迟数据的问题。
摘要由CSDN通过智能技术生成

1. window 概念

  • 1.1 为什么要有窗口
    • 实时流是源源不断的进行处理,为了得到一段流的处理后结果,此时就要把无限流转为有界流,此时Flink引入了窗口的概念
  • 1.2 什么叫窗口
    • 实时流上截取的一段流 就叫一个窗口
  • 1.3 开窗的原理
    • 将流数据发到有限大小的桶中进行分析.例如每小时的数据开窗,8~9的数据为一个桶 ,9~10点的数据为一个桶。那么8~9的数据来了 那么就会放在8~9的桶中。

2. window 类型

  • 2.1 时间窗口
    • 滚动时间窗口
      • 窗口长度固定 且连续 不可以有重叠
    • 滑动时间窗口
      • 窗口长度固定 可以有重叠
    • 会话窗口
      • 一段时间没有接受到新数据 就会生成新的窗口 时间无对其
  • 2.2 计数窗口(根据窗口中key的个数来开窗)
    • 滚动计数窗口
    • 滑动计数窗口

3. 怎么新建窗口

  • keyBy( ) 之后通过 timeWindows 和 countWindow方法 定义时间窗口和计数窗口

4. 新建窗口后的聚合方式

  • 4.1 增量聚合函数
    • 每条数据到来就进行计算,保存一个中间聚合状态,等到窗口结束 输出结果

    ReduceFunction AggreateFunction 等

  • 4.2 全窗口函数
    • 先把窗口所有的数据收集起来,等到计算的时候遍历所有数据 可以拿到当前窗口的元数据信息(窗口的开始时间 结束时间 窗口的key)

    ProcessWindownFunction / windowFunction

    • 窗口元数据信息
   
@PublicEvolving
public class TimeWindow extends Window {

	private final long start;
	private final long end;

	public long getStart() {
		return start;
	}

	public long getEnd() {
		return end;
	}

	@Override
	public long maxTimestamp() {
		return end - 1;
	}
  • 例子:

public class WindowTest1_TimeWindow {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

//        // 从文件读取数据
//        DataStream<String> inputStream = env.readTextFile("D:\\Projects\\BigData\\FlinkTutorial\\src\\main\\resources\\sensor.txt");

        // socket文本流
        DataStream<String> inputStream = env.socketTextStream("localhost", 7777);

        // 转换成SensorReading类型
        DataStream<SensorReading> dataStream = inputStream.map(line -> {
            String[] fields = line.split(",");
            return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
        });

        // 开窗测试

        // 1. 增量聚合函数
        DataStream<Integer> resultStream = dataStream.keyBy("id")
//                .countWindow(10, 2);
//                .window(EventTimeSessionWindows.withGap(Time.minutes(1)));
//                .window(TumblingProcessingTimeWindows.of(Time.seconds(15)))
                .timeWindow(Time.seconds(15))
                //输入类型(IN)、累加器类型(ACC)和输出类型(OUT)
                .aggregate(new AggregateFunction<SensorReading, Integer, Integer>() {
                    @Override
                    public Integer createAccumulator() {
                        return 0;
                    }

                    @Override
                    public Integer add(SensorReading value, Integer accumulator) {
                        return accumulator + 1;
                    }

                    @Override
                    public Integer getResult(Integer accumulator) {
                        return accumulator;
                    }

                    @Override
                    public Integer merge(Integer a, Integer b) {
                        return a + b;
                    }
                });

        // 2. 全窗口函数
        SingleOutputStreamOperator<Tuple3<String, Long, Integer>> resultStream2 = dataStream.keyBy("id")
                .timeWindow(Time.seconds(15))
//                .process(new ProcessWindowFunction<SensorReading, Object, Tuple, TimeWindow>() {
//                })

                .apply(new WindowFunction<SensorReading, Tuple3<String, Long, Integer>, Tuple, TimeWindow>() {
                    @Override
					// tuple 为窗口的key 
					// window 为窗口元数据的包装
                    public void apply(Tuple tuple, TimeWindow window, Iterable<SensorReading> input, Collector<Tuple3<String, Long, Integer>> out) throws Exception {
                        String id = tuple.getField(0);
                        Long windowEnd = window.getEnd();
                        Integer count = IteratorUtils.toList(input.iterator()).size();
                        out.collect(new Tuple3<>(id, windowEnd, count));
                    }
                });

        // 3. 其它可选API
        OutputTag<SensorReading> outputTag = new OutputTag<SensorReading>("late") {
        };

        SingleOutputStreamOperator<SensorReading> sumStream = dataStream.keyBy("id")
                .timeWindow(Time.seconds(15))
//                .trigger()
//                .evictor()
                .allowedLateness(Time.minutes(1))
                .sideOutputLateData(outputTag)
                .sum("temperature");

        sumStream.getSideOutput(outputTag).print("late");

        resultStream2.print();


        env.execute();
    }
}

  • 增量函数和全窗口函数结合使用

ReduceFunction/AggregateFunction和ProcessWindowFunction结合使用,分配到某个窗口的元素将被提前聚合,而当窗口的trigger触发时,也就是窗口收集完数据关闭时,将会把聚合结果发送到ProcessWindowFunction中,这时Iterable参数将会只有一个值,就是前面聚合的值


input
  .keyBy(...)
  .timeWindow(...)
  .reduce(
    incrAggregator: ReduceFunction[IN],
    function: ProcessWindowFunction[IN, OUT, K, W])

input
  .keyBy(...)
  .timeWindow(...)
  .aggregate(
    incrAggregator: AggregateFunction[IN, ACC, V],
    windowFunction: ProcessWindowFunction[V, OUT, K, W])

5. 其它可选 API

  • trigger() —— 触发器:定义 窗口什么时候关闭,触发计算并输出结果
  • evitor() —— 移除器:定义移除某些数据的逻辑
  • allowedLateness() —— 允许处理迟到的数据

设置窗口的延迟关闭的时间。
设置此目的:是为了hold住watermark没有hold住的迟到数据?
原理:如果没设置 那么到达窗口关闭时间 如果是全窗口那么触发计算,输出结果 如果是增量聚合 输出增量聚合的结果 销毁窗口。如果后面该窗口的数据 来了 那么就会被丢弃。为了避免这种现象,设置窗口延迟关闭时间,达到窗口关闭时间 输出聚合的结果,先不关闭窗口 在之后来了该窗口的数据,来一条聚合一条输出。

  • sideOutputLateData() —— 将迟到的数据放入侧输出流

如果真有那种少量特别晚的数据 直接单独收集起来。特别处理

  • getSideOutput() —— 获取侧输出流

6. Flink中的时间语义

  • 6.1 Flink中的时间语义有哪些?
    • 在 Flink 的流式处理中,会涉及到时间的不同概念
      • Event time 数据中携带的时间
      • Ingestion time 进入Flink系统的时候
      • Processing time 处理时间
  • 6.2 Flink中哪些时间比较重要呢?
    • 在 Flink 的流式处理中,绝大部分的业务都会使用 eventTime,一般只在
      eventTime 无法使用时,才会被迫使用 ProcessingTime 或者 IngestionTime。
  • 6.3 设置Event Time
StreamExecutionEnvironment env = 
StreamExecutionEnvironment.getExecutionEnvironment
// 从调用时刻开始给 env 创建的每一个 stream 追加时间特征
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

  • 6.4 如果EventTime作为时间窗口处理数据会有什么问题?

数据在网络中分布式传输 可能由于先发送的事件后到,所以在处理数据的时候可能会丢或者处理多余数据。例如一下图 以事件发生时间5s开一个窗口, 事件时间是5s的时候 准备关闭窗口 输出结果时,但是由于网络传输的原因 3 和2 的数据还没有接受。此时关闭窗口 那么就2 和 3 的窗口就不会被处理。

在这里插入图片描述

  • 6.5 为了解决延迟数据 引入了watermark的概念

7. watermark

  • 7.1 watermark是什么
    • 是一个时间戳 表示小于这个时间戳的事件已经到达了
    • 可以在数据源定义 也可以在任何算子中定义
    • 是一个StreamElement 和普通数据一起在算子之间传递
    • 触发窗口计算

    什么时候会触发窗口计算
    watermark=当前数据的最大事件时间-延迟时间

  • 7.2 watermark 就一定能保证延迟的数据不影响最终结果吗?

假如窗口已经关闭了 此时又来了 属于该窗口的数据,那么此时数据将被丢弃掉 印象最终的结果。为了解决这种问题,所以可设置窗口延迟关闭时间,当窗户到达设置的窗口时间大小后,输出结果 但是并不关闭窗口,在一定的延迟时间内,如果有该窗口的数据 每来一条 处理一条。如果没有延迟窗口没有兜住,那么就将结果扔到侧输出流中.

  • 7.3 watermark的特点
    • watermark 必须单调递增,以确保任务的事件时间时钟在向前推进,而不是在后退
  • 7.4 生成watermark的两种方式
    • AssignerWithPeriodicWatermarks : 周期性生成watermark ,默认是每隔200毫秒生成一个watermark
// 可以自定义
env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime);
env.getConfig().setAutoWatermarkInterval(200L);
 
 // 默认为200ms
public void setStreamTimeCharacteristic(TimeCharacteristic characteristic) {
   this.timeCharacteristic = Preconditions.checkNotNull(characteristic);
   if (characteristic == TimeCharacteristic.ProcessingTime) {
      getConfig().setAutoWatermarkInterval(0);
   } else {
      getConfig().setAutoWatermarkInterval(200);
   }
}

生成原理:

假如设置5s生成watermark 那么flink每隔5s会去调用getCurrentWatermark方法 判断在上一个watermark之后产生的数据中最大事件时间减延迟时间为当前watermark,如果当前watermark大于上一个生成的事件戳插入到数据中。否则不插入。

	// 对于乱序数据 源码中生成watermark的方法
	public final Watermark getCurrentWatermark() {
		// this guarantees that the watermark never goes backwards.
		// currentMaxTimestamp 上一个watermark之后最大的事件时间
		long potentialWM = currentMaxTimestamp - maxOutOfOrderness;
		if (potentialWM >= lastEmittedWatermark) {
			lastEmittedWatermark = potentialWM;
		}
		return new Watermark(lastEmittedWatermark);
	}

@Override
// 对于有序数据 watermark=currentTimestamp-1 
// 延迟1毫秒 
public final Watermark getCurrentWatermark() {
		return new Watermark(currentTimestamp == Long.MIN_VALUE ? Long.MIN_VALUE : currentTimestamp - 1);
	}
  • AssignerWithPunctuatedWatermarks : 断点性生成,每个数据来了 都去提取数据中的时间戳 与上一个watermark比较,判断要不要把新生成的watermark插入到数据流中
    • 7.4 watermark怎么使用
 DataStream<SensorReading> dataStream = inputStream.map(line -> {
            String[] fields = line.split(",");
            return new SensorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
        })
 // 升序数据根据数据提取事件时间 根据事件事件设置watermark 数据有顺序就不需要设置watemark
//                .assignTimestampsAndWatermarks(new AscendingTimestampExtractor<SensorReading>() {
//                    @Override
//                    public long extractAscendingTimestamp(SensorReading element) {
//                        return element.getTimestamp() * 1000L;
//                    }
//                })
                // 乱序数据设置时间戳和watermark
                .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<SensorReading>(Time.seconds(2)) {
                    @Override
                    public long extractTimestamp(SensorReading element) {
                        return element.getTimestamp() * 1000L;
                    }
                });

        OutputTag<SensorReading> outputTag = new OutputTag<SensorReading>("late") {
        };

  • 7.5 watermark是怎样在不同的算子间传递的
    • 取当前分区中 最小的watermark广播给下游

    • 传递过程如下:
      在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值