测试代码:
object MaxValue extends Serializable{
var max = 0
}
object Test {
def main(args: Array[String]): Unit = {
val sc = new SparkContext
val ssc = new StreamingContext(sc, Seconds(5))
val seq = Seq("testData")
val rdd = ssc.sparkContext.parallelize(seq)
val inputDStream = new ConstantInputDStream(ssc, rdd)
inputDStream.foreachRDD(rdd => { MaxValue.max = 10 }) //I change MaxValue.max value to 10.
val map = inputDStream.map(a => MaxValue.max)
map.print //Why the result is 0? Why not 10?
ssc.start
ssc.awaitTermination
}
}
在这种情况下,如何在 foreachRDD()中更改MaxValue.max
的值? map.print
的结果为0,为什么不是10.我想在 foreachRDD()中使用 RDD.max() ,所以我需要在 foreachRDD()中更改MaxValue.max
值。
答案 0 :(得分:3)
这是不可能的。请记住,RDD方法内部的操作是分布式运行的。因此,MaxValue.max
的更改只会在工作人员上执行,而不是在驱动程序上执行。也许如果你说你想要做什么可以帮助导致更好的解决方案,可能使用累加器?
答案 1 :(得分:2)
一般来说,最好避免尝试以这种方式累积值,有不同的方法,如累加器或updateStateByKey
可以正确地执行此操作。
为了更好地了解代码中发生的事情,假设您有多个执行程序分配了1个驱动程序和多个分区(最常见的情况)
inputDStream.foreachRDD(rdd => { MaxValue.max = 10 })
foreachRDD
中的代码块在驱动程序上运行,因此它会更新驱动程序上的对象MaxValue
val map = inputDStream.map(a => MaxValue.max)
将分别在每个执行程序上运行lambda,因此将从执行程序的MaxValue获取值(之前从未更新过)。另请注意,每个执行程序都有自己的MaxValue对象版本,因为每个执行程序都位于单独的JVM进程中(通常也位于集群内的单独节点上)。
将代码更改为
时 val map = inputDStream.map(a => {MaxValue.max=10; MaxValue.max})
你实际上在执行程序上更新MaxValue,然后在执行程序上获取它 - 所以它的工作原理。
这也应该有效:
val map = inputDStream.map(a => {MaxValue.max=10; a}).map(a => MaxValue.max)
但是,如果您执行以下操作:
val map = inputDStream.map(a => {MaxValue.max= new Random().nextInt(10); a}).map(a => MaxValue.max)
你应该获得具有4个不同整数的记录集(每个分区将具有不同的MaxValue)
要避免的理由是,根据具体情况,您可以获得更不可预测的结果。例如,如果您运行在群集上返回0 的原始代码,它将以本地模式返回10 ,就像在这种情况下驱动程序一样,并且所有分区都将存在于单个JVM进程中将分享这个对象。所以你甚至可以在这样的代码上创建单元测试,感觉安全但是在部署到集群时 - 开始遇到问题。
对于这个我不是100%肯定 - 试图在源代码中找到,但可能会出现另一个问题。在您的代码中,您将有2个工作:
一个是基于你的输出
inputDStream.foreachRDD
另一个基于map.print
输出。尽管他们最初使用相同的流,但Spark会为他们生成两个独立的DAG,并将安排两个独立的工作,可以完全独立地处理spark,事实上 - 它甚至不必保证工作的执行顺序(它确实保证明显在工作中执行阶段的顺序)如果理论上发生这种情况,它可以在第一个工作之前运行第二个工作,使结果更难以预测