flink窗口和时间

概要

基于flink 1.9.1版本和blink palner。

窗口分类

window (窗口)

对流中所有事件聚合是不可能的,因为通常流是无效的。所以需要window来划定范围。window是一种可以把无限数据切割为有限数据块的手段

根据类型,window有2种:

  1. 基于时间驱动的 time window,比如最近30s内。
  2. 基于数据驱动的 count window,比如最近100个元素。

根据是否重叠,窗口分如下2种:

  1. tumbling windows。滚动窗口,窗口之间不重叠,比如统计5分钟内的数据,窗口大小为5分钟。
  2. sliding windows。滑动窗口,窗口之间会重叠。比如每10秒统计最近20秒内的数据,窗口大小为20秒,每次窗口开始时间滑动10s。

使用例子:

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;

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

        DataStreamSource text = env.socketTextStream("10.10.40.33", 8000);

        DataStream> words = text.flatMap(new FlatMapFunction>() {
            @Override
            public void flatMap(String value, Collector> out) throws Exception {
                String[] split = value.split("\\s+");
                for (String word : split) {
                    out.collect(new Tuple2<>(word, 1));
                }
            }
        });

        // 时间窗口,timeWindow没有第2个参数,表示滚动窗口。
        words.keyBy(0).timeWindow(Time.seconds(5)).sum(1).print();

        // 数据窗口
        words.keyBy(0).countWindow(10).sum(1).print();

        // 如果没有执行keyBy,需要使用timeWindowAll。
        words.timeWindowAll(Time.seconds(5)).sum(1).print();

        env.execute("window");
    }
}

window 聚合方式

增量聚合

窗口中每进入一条数据,就进行一次计算。reduce, aggregate、sum、min、max等函数都是增量聚合。

全量聚合

等属于窗口的数据到齐,才开始进行聚合计算。如何窗口计算涉及到数据排序,必须使用全量聚合。主要的函数有,apply(windowFunction)和processWindowFunction

使用实例:

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;

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

        DataStreamSource text = env.socketTextStream("10.10.40.33", 8000);

        DataStream> words = text.flatMap(new FlatMapFunction>() {
            @Override
            public void flatMap(String value, Collector> out) throws Exception {
                String[] split = value.split("\\s+");
                for (String word : split) {
                    out.collect(new Tuple2<>(word, 1));
                }
            }
        });

        // 全量聚合。
        words.keyBy(0).timeWindow(Time.seconds(5))
                .apply(new WindowFunction, Object, Tuple, TimeWindow>() {
                    // 参数说明
                    // window 表示当前窗口
                    // input 是这一个窗口内全部数据的迭代器
                    // out 表示收集的数据
                    // tuple 表示聚合的key
                    @Override
                    public void apply(Tuple tuple, TimeWindow window, Iterable> input, Collector out) throws Exception {
                        int count = 0;

                        for (Tuple2 item : input) {
                            count++;
                        }

                        out.collect("key:" + tuple + ", window:" + window + ", count:" + count);
                    }
                })
                .print();
        env.execute("window");
    }
}

 
  

时间

stream中有3中时间:

  • event time:时间产生的时间
  • Ingestion time:事件进入Flink的时间
  • Processing Time:事件被处理时当前系统的时间

在flink中,时间默认是Processing Time,可以通过代码修改:

 env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
 env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
 env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime);

Watermarks

在使用eventTime的时候,可能会存在数据乱序问题。比如在使用kafka的时候,数据发送到了多个分区中,这个时候flink获取的数据就可能是乱序的。watermark就是为了解决数据乱序问题。

watermark可以翻译为水位线,通过定义一个容许的最大乱序时间,flink收集这个时间内的数据,并可以将这些数据进行重新排序。

代码实例:

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.watermark.Watermark;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.streaming.api.windowing.windows.Window;
import org.apache.flink.util.Collector;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;

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

        DataStreamSource text = env.socketTextStream("10.10.40.33", 8000);
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        DataStream> input = text.map(new MapFunction>() {
            @Override
            public Tuple2 map(String value) throws Exception {
                String[] split = value.split(",");
                return new Tuple2<>(split[0], Long.parseLong(split[1]));
            }
        });

        // 生成水印
        DataStream> waterMarkStream = input.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks>() {
            // 保存当前最大的日志时间
            Integer maxEventTime = 0;
            // 允许的最大乱序时间
            Long maxOutOfoOrderness = 1000L;

            /**
             * 定义生成water mark的逻辑
             * 默认情况下100ms 生成1次。
             *
             * water mark的时间,应该是最大的event time -  允许的最大乱序时间
             */
            @Nullable
            @Override
            public Watermark getCurrentWatermark() {
                return new Watermark(maxEventTime - maxOutOfoOrderness);
            }

            // 获取时间的时间
            @Override
            public long extractTimestamp(Tuple2 element, long previousElementTimestamp) {
                return element.f1;
            }
        });

        waterMarkStream.keyBy(0)
                .window(TumblingEventTimeWindows.of(Time.seconds(3))) // TumblingEventTimeWindows表示使用event time
                .apply(new WindowFunction, Long, Tuple, TimeWindow>() {

                    // 对window内的数据进行排序
                    @Override
                    public void apply(Tuple tuple, TimeWindow window, Iterable> input, Collector out) throws Exception {
                        String key = tuple.toString();
                        ArrayList arrayList = new ArrayList<>();
                        for (Tuple2  item: input) {
                            arrayList.add(item.f1);
                        }
                        Collections.sort(arrayList);

                        // 返回最后一条记录的时间
                        out.collect(arrayList.get(arrayList.size() - 1));
                    }
                }).print();
        env.execute("window");
    }
}

在watermark中,window的触发时间

  • 收集,根据window大小,见数据发分为多个时间段,区间是“[start, end)”。
  • 生成的watermar看时间是,最大的eventtime - 允许的最大乱序时间。
  • 当watermark时间大于等于窗口区间的end时间时候,窗口才会进行计算。反过来说,窗口要执行计算,当前的窗口的end时间需要小于watermark时间。

上面的例子来说含义就是:3s内的数据,有可能延迟10s到达,于是flink等待10s中。

延迟数据的处理

默认情况下,如何收到数据的event time 小于 wartermark的时间,这些数据会被直接丢弃。

可以通过allowedLateness设置数据最大延迟时间:

window(TumblingEventTimeWindows.of(Time.seconds(3))) // TumblingEventTimeWindows表示使用event time
allowedLateness(Time.seconds(2))

设置这个后,watermark时间2秒内的数据,任然会触发计算;watermark时间2秒外的数据,任然会被丢弃。简单的说,window触发时间是:watermark < window_end_time + allowedLateness 时间内

还可以通过sideOutputLateData,收集延迟的数据保存起来,方便定位问题。

//保存被丢弃的数据
OutputTag> outputTag = new OutputTag>("late-data"){};

waterMarkStream.keyBy(0)
    .window(TumblingEventTimeWindows.of(Time.seconds(3)))//按照消息的EventTime分配窗口,和调用TimeWindow效果一样
    .sideOutputLateData(outputTag) // 设置output
    

//丢弃数据处理
//把迟到的数据暂时打印到控制台,实际中可以保存到其他存储介质中
DataStream> sideOutput = window.getSideOutput(outputTag);
sideOutput.print();

多并行度下的watermark

多并行度下,每个线程都有一个watermark。flink对齐会取所有线程最小的watermark

flink sql 下的时间

以event-time 来说明。

窗口触发时间

假定窗口: [window_start, window_end)
watermark延迟时间: window_delay
数据最大延迟时间:

window 10s, 延迟2s

{“word”: “x”, “ts”: “2019-12-12 18:00:01”}
{“word”: “x”, “ts”: “2019-12-12 18:00:11”}

窗口结束后,处于历史窗口的数据不会聚合。
{“word”: “x”, “ts”: “2019-12-12 18:00:13”}

{“word”: “x”, “ts”: “2019-12-12 18:00:33”}
{“word”: “x”, “ts”: “2019-12-12 19:00:16”}

watermark 并非立即更新。有间隔时间,并且有
数据有之后才会更新。
{“word”: “x”, “ts”: “2019-12-12 18:00:13”}

输入{“word”: “x”, “ts”: “2019-12-12 18:00:01”}
2019-12-12 18:00:00的窗口还是会进行计算。

需要输入多条{“word”: “x”, “ts”: “2019-12-12 18:00:13”}
watermark才更新。

watermark更新,需要设置env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
env.getConfig.setAutoWatermarkInterval(entry.getPeriodicWatermarksInterval)
不设置这个watermark是不会更新。
就是窗口结束很久后,数据任然一直保留

若配置了AFTER WATERMARK 策略,需要显式地在TableConfig中配置minIdleStateRetentionTime标识能忍受的最大迟到时间。
minIdleStateRetentionTime在window中只影响窗口何时清除,不直接影响窗口何时触发, 例如配置为3600000,最多容忍1小时的迟到数据,超过这个时间的数据会直接丢弃

tableConfig.getMinIdleStateRetentionTime ,这个作为了allowLateness

watermark 的 delay ,定义的是数据最大乱序时间。也就是event_time 到达窗口结束时间,等待一定的时间才计算。

watermark >= 窗口结束时间,才会触发计算。

窗口结束后,allowLateness 定义数据最大的延迟时间。allowLateness 这段时间内到达的数据,
任然触发计算。

{“word”: “x”, “ts”: “2019-12-12 18:00:01”}

// watermark 更新到了09,任然小于 window_time
{“word”: “x”, “ts”: “2019-12-12 18:00:11”}
{“word”: “x”, “ts”: “2019-12-12 18:00:11”}
{“word”: “x”, “ts”: “2019-12-12 18:00:11”}
{“word”: “x”, “ts”: “2019-12-12 18:00:11”}

// 00窗口,任然完成了聚合
{“word”: “x”, “ts”: “2019-12-12 18:00:05”}

{“word”: “x”, “ts”: “2019-12-12 18:00:13”}
{“word”: “x”, “ts”: “2019-12-12 18:00:13”}
{“word”: “x”, “ts”: “2019-12-12 18:00:13”}
{“word”: “x”, “ts”: “2019-12-12 18:00:13”}

{“word”: “x”, “ts”: “2019-12-12 18:00:05”}

窗口的声明周期:

属于窗口的第一个元素到达,窗口就会创建
当时间(event-time/process-time), 超过窗口结束时间加允许的延迟时间
该窗口将被完全删除。

假设定义了,5分钟的滚动窗口,最大延迟时间为1分钟。
flink 创建了一个 12:00 到 12:05 的窗口,当watermark 经过 12:06的实时,窗口会被删除。

flink 只能删除基于时间的窗口,不能删除全局窗口。

{“word”: “x”, “ts”: “2019-12-12 18:00:59”}

public WindowOperatorBuilder withAllowedLateness(Duration allowedLateness) {
checkArgument(!allowedLateness.isNegative());
if (allowedLateness.toMillis() > 0) {
this.allowedLateness = allowedLateness.toMillis();
// allow late element, which means this window will send retractions
this.sendRetraction = true;
}
return this;
}

public KeyedProcessFunctionWithCleanupState(long minRetentionTime, long maxRetentionTime) {
		this.minRetentionTime = minRetentionTime;
		this.maxRetentionTime = maxRetentionTime;
		this.stateCleaningEnabled = minRetentionTime > 1; // minRetentionTime 小于1不会清理状态。
	}

	protected void initCleanupTimeState(String stateName) {
		if (stateCleaningEnabled) {
			ValueStateDescriptor inputCntDescriptor = new ValueStateDescriptor<>(stateName, Types.LONG);
			cleanupTimeState = getRuntimeContext().getState(inputCntDescriptor);
		}
	}

	protected void registerProcessingCleanupTimer(Context ctx, long currentTime) throws Exception {
		if (stateCleaningEnabled) {
			registerProcessingCleanupTimer(
				cleanupTimeState,
				currentTime,
				minRetentionTime,
				maxRetentionTime,
				ctx.timerService(

KeyedProcessFunctionWithCleanupState 管理是否清理状态。

minIdleStateRetentionTime 为0,窗口超过了watermark,窗口不会再触发,但是状态也不会清理。。。
这TMD是一个bug吧。

你可能感兴趣的:(flink)