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