Apache flink 如何避免Flink滑动窗口联接中的重复元组?

Apache flink 如何避免Flink滑动窗口联接中的重复元组?,apache-flink,Apache Flink,例如,有两条流。一是向用户展示广告。其中可以描述为(广告ID,显示时间戳)的元组。另一个是点击流--(广告ID,点击时间戳)。我们想得到一个加入流,其中包括所有的广告,是由用户点击后20分钟显示。我的解决方案是在一个滑动时间窗口上连接这两个流。但是在连接流中,有许多重复的元组。我怎样才能在新的流中只加入一个元组 stream1.join(stream2) .where(0) .equalTo(0) .window(SlidingTimeWindow

例如,有两条流。一是向用户展示广告。其中可以描述为(广告ID,显示时间戳)的元组。另一个是点击流--(广告ID,点击时间戳)。我们想得到一个加入流,其中包括所有的广告,是由用户点击后20分钟显示。我的解决方案是在一个滑动时间窗口上连接这两个流。但是在连接流中,有许多重复的元组。我怎样才能在新的流中只加入一个元组

stream1.join(stream2)
        .where(0)
        .equalTo(0)
        .window(SlidingTimeWindows.of(Time.of(30, TimeUnit.MINUTES), Time.of(10, TimeUnit.SECONDS)))

在代码中,您定义了一个重叠的滑动窗口(滑动窗口小于窗口大小)。如果不希望有重复项,可以通过仅指定窗口大小(默认幻灯片等于窗口大小)来定义非重叠窗口

解决方案1:

让flink支持在单独的窗口上连接两个流,如Spark streaming。在这种情况下,在广告流上实现SlidingTimeWindows(21分钟,1分钟),在Click stream上实现TupblingTimeWindows(1分钟),然后将这两个窗口化流连接起来

TupblingTimeWindows可以避免合并流中的重复记录。 21分钟大小的滑动时间窗口可以避免丢失合法点击。 一个问题是在加入的流中会有一些非法点击(20分钟后点击)。通过添加过滤器,可以轻松解决此问题

MultiWindowsJoinedStreams<Tuple2<String, Long>, Tuple2<String, Long>> joinedStreams =
            new MultiWindowsJoinedStreams<>(advertisement, click);

    DataStream<Tuple3<String, Long, Long>> joinedStream = joinedStreams.where(keySelector)
            .window(SlidingTimeWindows.of(Time.of(21, TimeUnit.SECONDS), Time.of(1, TimeUnit.SECONDS)))
            .equalTo(keySelector)
            .window(TumblingTimeWindows.of(Time.of(1, TimeUnit.SECONDS)))
            .apply(new JoinFunction<Tuple2<String, Long>, Tuple2<String, Long>, Tuple3<String, Long, Long>>() {
                private static final long serialVersionUID = -3625150954096822268L;

                @Override
                public Tuple3<String, Long, Long> join(Tuple2<String, Long> first, Tuple2<String, Long> second) throws Exception {
                    return new Tuple3<>(first.f0, first.f1, second.f1);
                }
            });

    joinedStream = joinedStream.filter(new FilterFunction<Tuple3<String, Long, Long>>() {
        private static final long serialVersionUID = -4325256210808325338L;

        @Override
        public boolean filter(Tuple3<String, Long, Long> value) throws Exception {
            return value.f1<value.f2&&value.f1+20000>=value.f2;
        }
    });
多窗口连接流连接流=
新的多窗口连接流(广告,点击);
DataStream joinedStream=joinedStreams.where(keySelector)
.window(slidengtimewindows.of(时间为(21,TimeUnit.SECONDS),时间为(1,TimeUnit.SECONDS)))
.equalTo(按键选择器)
.window(TumblingTimeWindows.of(Time.of(1,TimeUnit.SECONDS)))
.apply(新函数(){
私有静态最终长serialVersionUID=-3625150954096822268L;
@凌驾
公共Tuple3连接(Tuple2优先,Tuple2第二)引发异常{
返回新的Tuple3(first.f0、first.f1、second.f1);
}
});
joinedStream=joinedStream.filter(新的FilterFunction(){
私有静态最终长serialVersionUID=-4325256210808325338L;
@凌驾
公共布尔筛选器(Tuple3值)引发异常{
返回值。f1=value.f2;
}
});
解决方案2:

Flink支持无窗口的联接操作。连接运算符实现接口TwoInputStreamOperator保留这两个流的两个缓冲区(基于时间长度),并输出一个连接流

DataStream<Tuple2<String, Long>> advertisement = env
            .addSource(new FlinkKafkaConsumer082<String>("advertisement", new SimpleStringSchema(), properties))
            .map(new MapFunction<String, Tuple2<String, Long>>() {
                private static final long serialVersionUID = -6564495005753073342L;

                @Override
                public Tuple2<String, Long> map(String value) throws Exception {
                    String[] splits = value.split(" ");
                    return new Tuple2<String, Long>(splits[0], Long.parseLong(splits[1]));
                }
            }).keyBy(keySelector).assignTimestamps(timestampExtractor1);

    DataStream<Tuple2<String, Long>> click = env
            .addSource(new FlinkKafkaConsumer082<String>("click", new SimpleStringSchema(), properties))
            .map(new MapFunction<String, Tuple2<String, Long>>() {
                private static final long serialVersionUID = -6564495005753073342L;

                @Override
                public Tuple2<String, Long> map(String value) throws Exception {
                    String[] splits = value.split(" ");
                    return new Tuple2<String, Long>(splits[0], Long.parseLong(splits[1]));
                }
            }).keyBy(keySelector).assignTimestamps(timestampExtractor2);

    NoWindowJoinedStreams<Tuple2<String, Long>, Tuple2<String, Long>> joinedStreams =
            new NoWindowJoinedStreams<>(advertisement, click);
    DataStream<Tuple3<String, Long, Long>> joinedStream = joinedStreams
            .where(keySelector)
            .buffer(Time.of(20, TimeUnit.SECONDS))
            .equalTo(keySelector)
            .buffer(Time.of(5, TimeUnit.SECONDS))
            .apply(new JoinFunction<Tuple2<String, Long>, Tuple2<String, Long>, Tuple3<String, Long, Long>>() {
                private static final long serialVersionUID = -5075871109025215769L;

                @Override
                public Tuple3<String, Long, Long> join(Tuple2<String, Long> first, Tuple2<String, Long> second) throws Exception {
                    return new Tuple3<>(first.f0, first.f1, second.f1);
                }
            });
DataStream广告=env
.addSource(新的FlinkKafkaConsumer082(“广告”,新的SimpleStringSchema(),属性))
.map(新的映射函数(){
私有静态最终长serialVersionUID=-6564495005753073342L;
@凌驾
公共元组2映射(字符串值)引发异常{
字符串[]splits=value.split(“”);
返回新的Tuple2(拆分[0],Long.parseLong(拆分[1]);
}
}).keyBy(keySelector).assignTimestamps(timestampExtractor1);
DataStream click=env
.addSource(新的FlinkKafkaConsumer082(“单击”,新的SimpleStringSchema(),属性))
.map(新的映射函数(){
私有静态最终长serialVersionUID=-6564495005753073342L;
@凌驾
公共元组2映射(字符串值)引发异常{
字符串[]splits=value.split(“”);
返回新的Tuple2(拆分[0],Long.parseLong(拆分[1]);
}
}).keyBy(keySelector).assignTimestamps(timestampExtractor2);
NoWindowJoinedStreams joinedStreams=
新的NoWindowJoinedStreams(广告,点击);
DataStream joinedStream=joinedStreams
.where(键选择器)
.buffer(时间为(20,时间单位为秒))
.equalTo(按键选择器)
.buffer(时间单位(5秒))
.apply(新函数(){
私有静态最终长serialVersionUID=-5075871109025215769L;
@凌驾
公共Tuple3连接(Tuple2优先,Tuple2第二)引发异常{
返回新的Tuple3(first.f0、first.f1、second.f1);
}
});

我基于Flink streaming API TwoInputTransformation实现了两个新的连接操作符。请查收。我将向该存储库添加更多测试

在搜索同一问题的解决方案时,我发现“Interval Join”非常有用,它不会重复输出相同的元素。这是:

DataStream orangeStream=。。。
数据流绿色流=。。。
橙汁精
.keyBy()
.intervalJoin(greenStream.keyBy())
.between(时间毫秒(-2),时间毫秒(1))

过程(新ProcessJoinFunction在这种情况下,非重叠窗口有一个问题。如果在第一个窗口的末尾有一个播发元组,而对应的click元组在第二个窗口的开头。那么在连接的流中,可能会丢失一些数据。我明白你的意思。但是,如果你使用如果窗口中前10秒的元组与下一个窗口的最后10秒的元组相匹配,则也可以使用10秒的幻灯片。若要不错过任何匹配的元组,您需要使用一个元组的幻灯片为创建一个30秒的窗口。为避免重复,您还可以指定一个
逐出器
:感谢您的建议。时间窗口一个元组的幻灯片对我来说很有意义。虽然我似乎无法避免使用逐出器的重复。也许你可以使用
cogroup
而不是join。使用cogroup可以在应该联接的两个输入的窗口上使用迭代器。内部联接实现在cogroup中执行嵌套循环来计算联接。如果尤伊