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

Apache flink Apache Flink是否支持具有相同时间戳的多个事件?,apache-flink,flink-streaming,Apache Flink,Flink Streaming,在某些场景中,ApacheFlink似乎无法很好地处理具有相同时间戳的两个事件 根据文档,水印t表示任何新事件的时间戳将严格大于t。除非您完全排除两个事件具有相同时间戳的可能性,否则您将无法安全地发出t水印。强制使用不同的时间戳还将系统每秒可处理的事件数限制为1000 这真的是ApacheFlink中的一个问题还是有解决办法 对于那些希望使用具体示例的人,我的用例是为事件时间顺序流构建每小时聚合滚动字数。对于我在文件中复制的数据示例(请注意重复的9): 以及守则: public static v

在某些场景中,ApacheFlink似乎无法很好地处理具有相同时间戳的两个事件

根据文档,水印
t
表示任何新事件的时间戳将严格大于
t
。除非您完全排除两个事件具有相同时间戳的可能性,否则您将无法安全地发出
t
水印。强制使用不同的时间戳还将系统每秒可处理的事件数限制为1000

这真的是ApacheFlink中的一个问题还是有解决办法

对于那些希望使用具体示例的人,我的用例是为事件时间顺序流构建每小时聚合滚动字数。对于我在文件中复制的数据示例(请注意重复的9):

以及守则:

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));
    }
}

注意0-9处的dylan=2,它应该是1。

不,流元素具有相同的时间戳没有问题。但水印是一种断言,即所有后续事件的时间戳都将大于水印,因此这确实意味着您无法在时间t安全地为流元素发出水印t,除非流中的时间戳是严格单调递增的——如果有多个事件具有相同的时间戳,则情况并非如此。这就是为什么
AscendingTimestampExtractor
生成等于currentTimestamp-1的水印,您也应该这样做

请注意,您的应用程序实际上报告dylan=2在0-10,而不是0-9。这是因为dylan在时间11产生的水印触发了第一个计时器(计时器设置为时间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时已经有了两个Dylan。但是,
PrintlnSink
制作的报告具有误导性

要使示例按预期工作,需要更改两件事。首先,水印需要满足水印契约,而目前情况并非如此;其次,加窗逻辑不太正确。ProcessFunction需要为“dylan 11”事件做好准备,以便在计时器关闭窗口0-9之前到达。这是因为“dylan 11”流元素位于流中由其生成的水印之前

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

  • 跟踪当前窗口何时结束
  • 不要增加计数器,而是将当前窗口之后的事件添加到列表中
  • 在一个窗口结束后,使用该列表中属于下一个窗口的事件

  • 不,具有相同时间戳的流元素没有问题。但水印是一种断言,即所有后续事件的时间戳都将大于水印,因此这确实意味着您无法在时间t安全地为流元素发出水印t,除非流中的时间戳是严格单调递增的——如果有多个事件具有相同的时间戳,则情况并非如此。这就是为什么
    AscendingTimestampExtractor
    生成等于currentTimestamp-1的水印,您也应该这样做

    请注意,您的应用程序实际上报告dylan=2在0-10,而不是0-9。这是因为dylan在时间11产生的水印触发了第一个计时器(计时器设置为时间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时已经有了两个Dylan。但是,
    PrintlnSink
    制作的报告具有误导性

    要使示例按预期工作,需要更改两件事。首先,水印需要满足水印契约,而目前情况并非如此;其次,加窗逻辑不太正确。ProcessFunction需要为“dylan 11”事件做好准备,以便在计时器关闭窗口0-9之前到达。这是因为“dylan 11”流元素位于流中由其生成的水印之前

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

  • 跟踪当前窗口何时结束
  • 不要增加计数器,而是将当前窗口之后的事件添加到列表中
  • 在一个窗口结束后,使用该列表中属于下一个窗口的事件

  • 谢谢@David的回答,不过我还有一个跟进。ProcessFunction如何为“dylan 11”事件做好“准备”?我已经看过了你之前的评论,我真的很喜欢使用TumblingEventWindow的想法——它似乎不适合我的目的,因为它没有包含我所追求的“滚动”(聚合)方面。你编辑它是因为它占用了太多的空间还是因为它有一个错误?无论如何,今天下午我会试试的!好的,我已经分享了——我从我的答案中删除了它,因为它很长,而且可能不是大家都感兴趣的。谢谢,我基本上理解了。第二个
    keyBy
    的原因对我来说并不明显。在哪里可以阅读更多关于state用法的信息?当前实现必须依赖内存中的状态,但mi希望更改该状态或使其成为查询状态
    (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)
    ...