Apache Flink是否支持具有相同时间戳的多个事件?

时间:2019-01-11 22:58:18

标签: apache-flink flink-streaming

在某些情况下,Apache Flink似乎无法很好地处理具有相同时间戳的两个事件。

根据文档,水印t表示任何新事件的时间戳都严格大于t。除非您可以完全放弃两个事件具有相同时间戳的可能性,否则将永远不会发出t水印。实施不同的时间戳也将系统每秒可处理的事件数限制为1000。

这真的是Apache Flink中的问题还是有解决方法?

对于那些想使用具体示例的人,我的用例是为事件时间有序流构建每小时汇总的滚动字数统计。对于我复制到文件中的数据样本(请注意重复的9):

mario 0
luigi 1
mario 2
mario 3
vilma 4
fred 5
bob 6
bob 7
mario 8
dan 9
dylan 9
dylan 11
fred 12
mario 13
mario 14
carl 15
bambam 16
summer 17
anna 18
anna 19
edu 20
anna 21
anna 22
anna 23
anna 24
anna 25

和代码:

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

    env.setStreamTimeCharacteristic(EventTime);


    String fileLocation = "full file path here";
    DataStreamSource<String> rawInput = env.readFile(new TextInputFormat(new Path(fileLocation)), fileLocation);

    rawInput.flatMap(parse())
            .assignTimestampsAndWatermarks(new AssignerWithPunctuatedWatermarks<TimestampedWord>() {
                @Nullable
                @Override
                public Watermark checkAndGetNextWatermark(TimestampedWord lastElement, long extractedTimestamp) {
                    return new Watermark(extractedTimestamp);
                }

                @Override
                public long extractTimestamp(TimestampedWord element, long previousElementTimestamp) {
                    return element.getTimestamp();
                }
            })
            .keyBy(TimestampedWord::getWord)
            .process(new KeyedProcessFunction<String, TimestampedWord, Tuple3<String, Long, Long>>() {
                private transient ValueState<Long> count;

                @Override
                public void open(Configuration parameters) throws Exception {
                    count = getRuntimeContext().getState(new ValueStateDescriptor<>("counter", Long.class));
                }

                @Override
                public void processElement(TimestampedWord value, Context ctx, Collector<Tuple3<String, Long, Long>> out) throws Exception {
                    if (count.value() == null) {
                        count.update(0L);
                        setTimer(ctx.timerService(), value.getTimestamp());
                    }

                    count.update(count.value() + 1);
                }

                @Override
                public void onTimer(long timestamp, OnTimerContext ctx, Collector<Tuple3<String, Long, Long>> out) throws Exception {
                    long currentWatermark = ctx.timerService().currentWatermark();
                    out.collect(new Tuple3(ctx.getCurrentKey(), count.value(), currentWatermark));
                    if (currentWatermark < Long.MAX_VALUE) {
                        setTimer(ctx.timerService(), currentWatermark);
                    }
                }

                private void setTimer(TimerService service, long t) {
                    service.registerEventTimeTimer(((t / 10) + 1) * 10);
                }
            })
            .addSink(new PrintlnSink());

    env.execute();
}

private static FlatMapFunction<String, TimestampedWord> parse() {
    return new FlatMapFunction<String, TimestampedWord>() {
        @Override
        public void flatMap(String value, Collector<TimestampedWord> out) {
            String[] wordsAndTimes = value.split(" ");
            out.collect(new TimestampedWord(wordsAndTimes[0], Long.parseLong(wordsAndTimes[1])));
        }
    };
}

private static class TimestampedWord {
    private final String word;
    private final long timestamp;

    private TimestampedWord(String word, long timestamp) {
        this.word = word;
        this.timestamp = timestamp;
    }

    public String getWord() {
        return word;
    }

    public long getTimestamp() {
        return timestamp;
    }
}

private static class PrintlnSink implements org.apache.flink.streaming.api.functions.sink.SinkFunction<Tuple3<String, Long, Long>> {
    @Override
    public void invoke(Tuple3<String, Long, Long> value, Context context) throws Exception {
        long timestamp = value.getField(2);
        System.out.println(value.getField(0) + "=" + value.getField(1) + " at " + (timestamp - 10) + "-" + (timestamp - 1));
    }
}

我明白了

    mario=4 at 1-10
    dylan=2 at 1-10
    luigi=1 at 1-10
    fred=1 at 1-10
    bob=2 at 1-10
    vilma=1 at 1-10
    dan=1 at 1-10
    vilma=1 at 10-19
    luigi=1 at 10-19
    mario=6 at 10-19
    carl=1 at 10-19
    bambam=1 at 10-19
    dylan=2 at 10-19
    summer=1 at 10-19
    anna=2 at 10-19
    bob=2 at 10-19
    fred=2 at 10-19
    dan=1 at 10-19
    fred=2 at 9223372036854775797-9223372036854775806
    dan=1 at 9223372036854775797-9223372036854775806
    carl=1 at 9223372036854775797-9223372036854775806
    mario=6 at 9223372036854775797-9223372036854775806
    vilma=1 at 9223372036854775797-9223372036854775806
    edu=1 at 9223372036854775797-9223372036854775806
    anna=7 at 9223372036854775797-9223372036854775806
    summer=1 at 9223372036854775797-9223372036854775806
    bambam=1 at 9223372036854775797-9223372036854775806
    luigi=1 at 9223372036854775797-9223372036854775806
    bob=2 at 9223372036854775797-9223372036854775806
    dylan=2 at 9223372036854775797-9223372036854775806

在0-9处通知dylan = 2,应为1。

1 个答案:

答案 0 :(得分:2)

不,具有相同时间戳的流元素没有问题。但是水印是一个断言,即随后发生的所有事件的时间戳都将大于水印,因此这确实意味着您不能在时间 t t 。 em>,除非流中的时间戳严格单调递增-如果存在多个具有相同时间戳的事件,则情况并非如此。这就是AscendingTimestampExtractor产生等于currentTimestamp-1的水印的原因,您应该这样做。

请注意,您的应用程序实际上在0-10而不是0-9上报告dylan = 2。这是因为在时间11由dylan生成的水印正在触发第一个计时器(将计时器设置为时间10,但是由于没有时间戳为10的元素,因此该计时器直到“ dylan 11”的水印才会触发到达)。您的PrintlnSink使用timestamp - 1表示时间跨度的上限,因此是11-1或10,而不是9。

您的ProcessFunction的输出看起来没有问题,如下所示:

(mario,4,11)
(dylan,2,11)
(luigi,1,11)
(fred,1,11)
(bob,2,11)
(vilma,1,11)
(dan,1,11)
(vilma,1,20)
(luigi,1,20)
(mario,6,20)
(carl,1,20)
(bambam,1,20)
(dylan,2,20)
...

的确,到11岁时已经有两个迪兰族。但是PrintlnSink产生的报告具有误导性。

需要进行两项更改才能使您的示例按预期工作。首先,水印需要满足水印合同,目前情况并非如此,其次,加窗逻辑并不完全正确。需要为关闭“ 0-9窗口”的计时器触发之前的“ dylan 11”事件到达做好准备的ProcessFunction。这是因为“ dylan 11”流元素在流中从其生成的水印之前。

更新:时间戳超出当前窗口的事件(例如“ dylan 11”)可以由

处理
  1. 跟踪当前窗口何时结束
  2. 将当前窗口之后的事件添加到列表中,而不是增加计数器
  3. 窗口结束后,使用该列表中的事件,这些事件将落入下一个窗口