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