Apache flink 通过合并,事件时间窗口的结束时间戳不能早于当前水印

Apache flink 通过合并,事件时间窗口的结束时间戳不能早于当前水印,apache-flink,amazon-kinesis,Apache Flink,Amazon Kinesis,我们已经创建了一个在AWS Kinesis Analytics中运行的流媒体Flink应用程序。它主要用于处理web点击流数据、页面浏览、会话等。。我们有一个来自Kinesis数据流的页面视图输入,该数据流被分割成由会话/设备令牌键控的键控窗口 该应用程序在小规模下运行良好,但当以我们预期的正常生产吞吐量(每天约100万次页面浏览量)进行测试时,我们在合并窗口时会定期遇到错误: “The end timestamp of an event-time window cannot become ea

我们已经创建了一个在AWS Kinesis Analytics中运行的流媒体Flink应用程序。它主要用于处理web点击流数据、页面浏览、会话等。。我们有一个来自Kinesis数据流的页面视图输入,该数据流被分割成由会话/设备令牌键控的键控窗口

该应用程序在小规模下运行良好,但当以我们预期的正常生产吞吐量(每天约100万次页面浏览量)进行测试时,我们在合并窗口时会定期遇到错误:

“The end timestamp of an event-time window cannot become earlier than the current watermark by merging.”
这个不支持的操作异常正在使我们的应用程序崩溃,当它重新启动时,它试图再次处理同一个窗口,并一次又一次地崩溃。我们已将此异常追溯到以下PR,但对于如何处理此案例,我们有点不知所措。我们的主要目标是防止应用程序崩溃或以任何方式损坏应用程序的状态

我们已尝试更改maxOutOfOrderness,以查看应用程序的行为是否不同,但尚未找到不会发生错误的场景,除非我们将其设置为非常低的数字,如1

/Create input data streams from kinesis data streams
    DataStream<String> pvInput;

    if (env.getIsLocal()) {
        pvInput = createLocalDataStream(streamEnv, "pv-stream", env);
    } else {
        pvInput = createAwsDataStream(streamEnv, env.get("pv-stream"), env);
    }

    ObjectMapper mapper = new ObjectMapper();

/* SOURCES AND INITIAL MAPPING */

    //Turn pageview strings into pageview objects and assign timestamps
    DataStream<PageView> mappedPvs = pvInput
            .map(value -> mapper.readValue(value, PageView.class)).uid("pv_mapper").name("PV Mapper")
            .filter(value -> value.timestamp != null && value.uuid != null).uid("pv_filter").name("PV Filter")
            .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<PageView>(Time.minutes(30)) {
                @Override
                public long extractTimestamp(PageView element) {
                    return element.timestamp.getTime();
                }
            }).uid("pv_timestamp_assigner").name("PV Timestamps");

/* SESSIONIZATION */

    //Key Pageviews by uuid for sessionization
    KeyedStream<PageView, String> keyedPvStream = mappedPvs
            .keyBy((KeySelector<PageView, String>) value -> value.uuid);

    long sessionWindow = 30L;

    //Window pageviews into sessions
    DataStream<PageViewAccumulator> sessionized = keyedPvStream
        .window(ActivitySessionAssigner.withGap(Time.minutes(sessionWindow)))
        .aggregate(new PageViewAggregateFunction()).uid("session_window").name("Session Window");

这个问题可以通过使用下面的ProcessFunction过滤延迟事件来解决。将此函数放在时间戳提取器和窗口函数之间可删除任何延迟事件,从而消除发生此错误的可能性

public class LateEventFilter extends ProcessFunction<PageView, PageView> {
    @Override
    public void processElement(PageView value, Context ctx, Collector<PageView> out) throws Exception {
        if(ctx.timestamp() > ctx.timerService().currentWatermark()){
            out.collect(value);
        }
    }
}
您还可以使用类似的函数将延迟事件输出到接收器,如下面的示例所示

public class LateEventSideOutput extends ProcessFunction<PageView, PageView> {
    @Override
    public void processElement(PageView value, Context ctx, Collector<PageView> out) throws Exception {
        if(ctx.timestamp() <= ctx.timerService().currentWatermark()) {
            out.collect(value);
        }
    }
}
将其全部连接起来会像这样:

DataStream<PageView> lateFilteredPvs = mappedPvs.process(new LateEventFilter()).uid("late_pv_filter").name("LatePvFilter");

DataStream<PageView> latePvs = mappedPvs.process(new LateEventSideOutput()).uid("late_pv").name("LatePv");
                l 
latePvs.addSink(latePvSink).uid("late_pv_sink").name("LatePvSink");

您是否尝试过使水印延迟大于会话间隔?例如,31分钟?我们没有,只是尝试了相同或更少的延迟。我们将尝试一下,并在这里分享结果。与31@DavidAnderson一起尝试,但我们得到了相同的错误消息。值得注意的是,在这个测试数据中,数据进入管道时可能会有一些相当大的延迟。上一次失败的水印时间为2019年4月16日星期二上午7:44:51.031,合并窗口的结束时间为2019年4月16日星期二上午6:14:37.667,相差略大于1H30米。我无法解释为什么在尝试此有问题的合并之前没有清除这样一个旧窗口,但是我认为你应该能够通过增加水印延迟到没有延迟事件的程度来避免这个问题;2增加允许迟到时间以适应实际迟到时间;或者3使用流程功能过滤延迟事件。感谢Dave,我们也感到困惑,我们将研究这些方法,看看是否能够解决这个问题。Andrew是我们团队的工程师,根据@DavidAnderson的建议实现了这一点。谢谢你,大卫!
DataStream<PageView> lateFilteredPvs = mappedPvs.process(new LateEventFilter()).uid("late_pv_filter").name("LatePvFilter");

DataStream<PageView> latePvs = mappedPvs.process(new LateEventSideOutput()).uid("late_pv").name("LatePv");
                l 
latePvs.addSink(latePvSink).uid("late_pv_sink").name("LatePvSink");