繁体   English   中英

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

[英]Does Apache Flink support multiple events with the same timestamp?

在某些情况下,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

注意dylan = 2在0-9处应为1。

不,具有相同时间戳的流元素没有问题。 但水印是一种说法,以后的所有事件都会有时间戳比水印更大,所以这是否意味着你不能安全地在时间t:发射一个流元素水印T,除非流中的时间戳是严格单调递增- -如果多个事件具有相同的时间戳,则情况并非如此。 这就是为什么AscendingTimestampExtractor生成等于currentTimestamp-1的水印的原因,您应该这样做。

请注意,您的应用程序实际上是在0-10而不是0-9处报告dylan = 2。 这是因为在时间11由dylan生成的水印正在触发第一个计时器(将计时器设置为时间10,但是由于没有时间戳为10的元素,因此该计时器直到“ dylan 11”的水印才会触发到达)。 并且您的PrintlnSink使用timestamp - 1表示时间跨度的上限,因此是PrintlnSink或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. 窗口结束后,使用该列表中的事件进入下一个窗口

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM