前言
在开发时发现一个事,rdd有foreach方法,rdd.collect之后也有foreach,这两个方法却大不一样。
1.1、代码示例
代码如下(示例):
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder
.master("local[*]")
.appName("test")
.getOrCreate()
val sc = spark.sparkContext;
val value = sc.makeRDD(List(1, 2, 3, 4),2)
.map(_*2);
value.collect().foreach(println);
println("**********")
value.foreach(println)
sc.stop()
}
输出:
2
4
6
8
**********
6
8
2
4
在本地为多线程(local[*])执行的时候,并且分区为2个以上的时候,rdd.foreach方法打印出来的数据顺序乱了!~
2.1、解释
如图所示,rdd.foreach方法在执行的过程中的打印方法是在Executor中执行的,每个Executor在执行完自己的逻辑之后就执行foreach进行打印,因此在本地多线程执行的时候,可能List(3,4)是有可能先执行完成,所以会存在顺序错乱的情况。
多线程,多分区才会有这种情况。
而value.collect().foreach(println)
这种写法的print是在数据采集到Driver之后,在Driver端打印的。所以顺序不会乱。
2.1 代码示例
RDD中集合有1,2,3,4,想通过foreach来进行累加,但是打印出来的结果却是0。这个就是累加器的坑。大家多多参考。
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder
.master("local[*]")
.appName("test")
.getOrCreate()
val sc = spark.sparkContext;
val value = sc.makeRDD(List(1, 2, 3, 4))
var sum = 0;
value.foreach(num=>{
sum+=num;
})
println("总和为:"+sum)
sc.stop()
}
输出:
总和为:0
在普通的java编程中或者其他语言的变成中, 这种写法是没有问题的,但是在RDD中,有Driver和Executor的概念,RDD的计算是在Executor中进行的。然而sum确是在Driver中产生的。
如上图所示,sum=0 会传递到两个Executor中,进行累加,两个Executor的sum没有各自累加自己的数据,但是sum的值并没有回调给Driver中,因此,Driver中的sum一直是0。
因此spark数据结构中有一个叫累加器。通过累加器即可完成此功能,代码如下:
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder
.master("local[*]")
.appName("test")
.getOrCreate()
val sc = spark.sparkContext;
val value = sc.makeRDD(List(1, 2, 3, 4))
val sum = sc.longAccumulator("sum");
value.foreach(num=>{
sum.add(num)
})
println("总和为:"+sum.value)
sc.stop()
}