Apache flink 为什么Flink会在数据流联接+;全球窗口?
我正在学习/试验Flink,我正在观察DataStream连接的一些意外行为,我想了解发生了什么 假设我有两个流,每个流有10条记录,我想在Apache flink 为什么Flink会在数据流联接+;全球窗口?,apache-flink,Apache Flink,我正在学习/试验Flink,我正在观察DataStream连接的一些意外行为,我想了解发生了什么 假设我有两个流,每个流有10条记录,我想在id字段中加入它们。让我们假设一个流中的每个记录在另一个流中都有一个匹配的记录,并且ID在每个流中都是唯一的。也就是说,我必须使用一个全局窗口(需求) 使用DataStream API(我在Scala中的简化代码)加入: 结果: 所有内容都按预期打印,第一个流中的每条记录与第二个流中的一条记录合并 然而: 如果我将其中一个记录(例如,带有更新的字段)从
id
字段中加入它们。让我们假设一个流中的每个记录在另一个流中都有一个匹配的记录,并且ID在每个流中都是唯一的。也就是说,我必须使用一个全局窗口(需求)
使用DataStream API(我在Scala中的简化代码)加入:
结果:
- 所有内容都按预期打印,第一个流中的每条记录与第二个流中的一条记录合并
- 如果我将其中一个记录(例如,带有更新的字段)从一个流重新发送到该流,则会发出两个重复的连接事件问题是,记录永远不会从全局窗口中删除。因此,只要有新记录到达,但旧记录仍然存在,就在全局窗口上触发联接操作
因此,为了让它在您的案例中运行,您需要实现一个定制的。我在一个最小的工作示例中扩展了您的示例,并添加了驱逐器,我将在代码片段之后解释
val data1 = List( (1L, "myId-1"), (2L, "myId-2"), (5L, "myId-1"), (9L, "myId-1")) val data2 = List( (3L, "myId-1", "myValue-A")) val stream1 = env.fromCollection(data1) val stream2 = env.fromCollection(data2) stream1.join(stream2) .where(_._2).equalTo(_._2) .window(GlobalWindows.create()) // assume this is a requirement .trigger(CountTrigger.of(1)) .evictor(new Evictor[CoGroupedStreams.TaggedUnion[(Long, String), (Long, String, String)], GlobalWindow](){ override def evictBefore(elements: lang.Iterable[TimestampedValue[CoGroupedStreams.TaggedUnion[(Long, String), (Long, String, String)]]], size: Int, window: GlobalWindow, evictorContext: Evictor.EvictorContext): Unit = {} override def evictAfter(elements: lang.Iterable[TimestampedValue[CoGroupedStreams.TaggedUnion[(Long, String), (Long, String, String)]]], size: Int, window: GlobalWindow, evictorContext: Evictor.EvictorContext): Unit = { import scala.collection.JavaConverters._ val lastInputTwoIndex = elements.asScala.zipWithIndex.filter(e => e._1.getValue.isTwo).lastOption.map(_._2).getOrElse(-1) if (lastInputTwoIndex == -1) { println("Waiting for the lookup value before evicting") return } val iterator = elements.iterator() for (index <- 0 until size) { val cur = iterator.next() if (index != lastInputTwoIndex) { println(s"evicting ${cur.getValue.getOne}/${cur.getValue.getTwo}") iterator.remove() } } } }) .apply((r, l) => (r, l)) .print()
最后一句话:如果表API已经提供了一种简洁的方式来做您想要的事情,我会坚持使用它,然后在需要时使用它。问题是,记录永远不会从全局窗口中删除。因此,只要有新记录到达,但旧记录仍然存在,就在全局窗口上触发联接操作 因此,为了让它在您的案例中运行,您需要实现一个定制的。我在一个最小的工作示例中扩展了您的示例,并添加了驱逐器,我将在代码片段之后解释val data1 = List( (1L, "myId-1"), (2L, "myId-2"), (5L, "myId-1"), (9L, "myId-1")) val data2 = List( (3L, "myId-1", "myValue-A")) val stream1 = env.fromCollection(data1) val stream2 = env.fromCollection(data2) stream1.join(stream2) .where(_._2).equalTo(_._2) .window(GlobalWindows.create()) // assume this is a requirement .trigger(CountTrigger.of(1)) .evictor(new Evictor[CoGroupedStreams.TaggedUnion[(Long, String), (Long, String, String)], GlobalWindow](){ override def evictBefore(elements: lang.Iterable[TimestampedValue[CoGroupedStreams.TaggedUnion[(Long, String), (Long, String, String)]]], size: Int, window: GlobalWindow, evictorContext: Evictor.EvictorContext): Unit = {} override def evictAfter(elements: lang.Iterable[TimestampedValue[CoGroupedStreams.TaggedUnion[(Long, String), (Long, String, String)]]], size: Int, window: GlobalWindow, evictorContext: Evictor.EvictorContext): Unit = { import scala.collection.JavaConverters._ val lastInputTwoIndex = elements.asScala.zipWithIndex.filter(e => e._1.getValue.isTwo).lastOption.map(_._2).getOrElse(-1) if (lastInputTwoIndex == -1) { println("Waiting for the lookup value before evicting") return } val iterator = elements.iterator() for (index <- 0 until size) { val cur = iterator.next() if (index != lastInputTwoIndex) { println(s"evicting ${cur.getValue.getOne}/${cur.getValue.getTwo}") iterator.remove() } } } }) .apply((r, l) => (r, l)) .print()
最后一句话:如果表API已经提供了一种简洁的方式来完成您想要的操作,我会坚持使用它,然后在需要时使用。您是否可以更具体地说明“如果我将一条记录(例如,带有更新的字段)从一个流重新发送到该流,将发出两个重复的连接事件。”你能举个简单的例子吗?例如,如果流1只有一条记录KeyValueRecord(1,10),流2只有一条KeyValueRecord(1,42),我的应用程序会打印[KeyValueRecord(1,10),KeyValueRecord(1,42)],因为两条记录都有相同的键“1”。如果以后,我将新记录KeyValueRecord(1,11)推送到流1,我的应用程序不仅会再次打印以前的[KeyValueRecord(1,10),KeyValueRecord(1,42)],而且还会打印[KeyValueRecord(1,11),KeyValueRecord(1,42)](我只希望是后者)。如果我再次推送相同的记录,它将打印相同的记录,再加上[KeyValueRecord(1,11),KeyValueRecord(1,42)],等等……您能否更具体地说“如果我将其中一个记录(例如,带有更新的字段)从一个流重新发送到该流,将发出两个重复的连接事件。”你能举个简单的例子吗?例如,如果流1只有一条记录KeyValueRecord(1,10),流2只有一条KeyValueRecord(1,42),我的应用程序会打印[KeyValueRecord(1,10),KeyValueRecord(1,42)],因为两条记录都有相同的键“1”。如果以后,我将新记录KeyValueRecord(1,11)推送到流1,我的应用程序不仅会再次打印以前的[KeyValueRecord(1,10),KeyValueRecord(1,42)],而且还会打印[KeyValueRecord(1,11),KeyValueRecord(1,42)](我只希望是后者)。如果我再推同一条记录,它会打印出同样的内容,再加上[KeyValueRecord(1,11),KeyValueRecord(1,42)],等等……谢谢你的详细回答!我将更深入地研究驱逐器:)我还尝试实现自己的KeyedCoProcessFunction来加入流,它也如预期的那样工作(尽管我更喜欢使用更高级的API)。再次感谢!谢谢你的详细回答!我将更深入地研究驱逐器:)我还尝试实现自己的KeyedCoProcessFunction来加入流,它也如预期的那样工作(尽管我更喜欢使用更高级的API)。再次感谢!Waiting for the lookup value before evicting Waiting for the lookup value before evicting Waiting for the lookup value before evicting Waiting for the lookup value before evicting 4> ((1,myId-1),(3,myId-1,myValue-A)) 4> ((5,myId-1),(3,myId-1,myValue-A)) 4> ((9,myId-1),(3,myId-1,myValue-A)) evicting (1,myId-1)/null evicting (5,myId-1)/null evicting (9,myId-1)/null