下面是我写的简单代码:
val env = StreamExecutionEnvironment.getExecutionEnvironment
val list = new ListBuffer[Tuple3[String,Int,Int]]
val random = new Random()
for(x <- 0 to 4){
if(random.nextBoolean()){
list.append(("INSERT",2,1))
} else {
list.append(("UPDATE",2,1))
}
}
val data = env.fromElements(list).flatMap(_.toList)
val keyed = data.keyBy(0).sum(1)
keyed.print()
val reKeyed = keyed.keyBy(0).sum(2)
reKeyed.print()
env.execute()
dataStream reKeyed 应该将 keyed 作为输入数据源。但是,打印的结果表明它们来自原始数据源。 如果第二次只调用 KeyBy 而不调用 sum 方法, 打印的结果是正确的。 所以有什么问题?
答案 0 :(得分:0)
问题在于,如果您两次调用keyBy
,则第二次调用将覆盖第一个调用,因此元素可能会出现在与以前不同的TaskManager上。
对于这种情况,您实际上是在说您实际上要使用DataStreamUtils.reinterpretAsKeyedStream
,它应该完全按照您描述的方式工作,这意味着它不应更改先前键控的Datastream
的分区。
答案 1 :(得分:0)
对于给定的代码段,我找不到任何错误,并怀疑您的期望与API不符。
我在源代码以及第一和第二分组求和中添加了一些打印语句。
source:1> (UPDATE,2,1)
source:1> (INSERT,2,1)
source:1> (UPDATE,2,1)
source:1> (UPDATE,2,1)
source:1> (INSERT,2,1)
first:3> (UPDATE,2,1)
first:2> (INSERT,2,1)
first:3> (UPDATE,4,1)
first:2> (INSERT,4,1)
first:3> (UPDATE,6,1)
second:2> (INSERT,2,1)
second:3> (UPDATE,2,1)
second:2> (INSERT,2,2)
second:3> (UPDATE,2,2)
second:3> (UPDATE,2,3)
如您所见,随机输入包含3条update和2条insert语句。因此,第一个keyBy
的结果正确显示了update,6,1
和insert,4,1
。
现在,该结果用作第二个keyBy
的输入,但是由于您正在第二列上求和,因此您第一项操作的结果将被丢弃。您可能希望第一个keyBy
的“最终”总和被作为第二列总和的基础记录。但这实际上始终是第一个记录作为基础,这是流设置中唯一可行的选择。
您真正想要拥有的是同一分组依据中两个字段的总和。不幸的是,流API并没有捷径,但是实现自己很容易。
val keyed = data.keyBy(0)
.reduce((tuple1, tuple2) => (tuple1._1, tuple1._2 + tuple2._2, tuple1._3 + tuple2._3))
keyed.print("first")
产生
source:4> (INSERT,2,1)
source:4> (INSERT,2,1)
source:4> (INSERT,2,1)
source:4> (UPDATE,2,1)
source:4> (INSERT,2,1)
first:3> (UPDATE,2,1)
first:2> (INSERT,2,1)
first:2> (INSERT,4,2)
first:2> (INSERT,6,3)
first:2> (INSERT,8,4)
由于对数据进行分组非常昂贵,因此该解决方案也更加有效。