http://www.aboutyun.com/forum.php?mod=viewthread&tid=18238
前言
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
/
/
需要对名为“hello.txt”的HDFS文件进行一次
map
操作,再进行一次
reduce
操作。也就是说,需要对一份数据执行两次算子操作。
/
/
错误的做法:对于同一份数据执行多次算子操作时,创建多个RDD。
/
/
这里执行了两次textFile方法,针对同一个HDFS文件,创建了两个RDD出来,然后分别对每个RDD都执行了一个算子操作。
/
/
这种情况下,Spark需要从HDFS上两次加载hello.txt文件的内容,并创建两个单独的RDD;第二次加载HDFS文件以及创建RDD的性能开销,很明显是白白浪费掉的。
val rdd1
=
sc.textFile(
"hdfs://192.168.0.1:9000/hello.txt"
)
rdd1.
map
(...)
val rdd2
=
sc.textFile(
"hdfs://192.168.0.1:9000/hello.txt"
)
rdd2.
reduce
(...)
/
/
正确的用法:对于一份数据执行多次算子操作时,只使用一个RDD。
/
/
这种写法很明显比上一种写法要好多了,因为我们对于同一份数据只创建了一个RDD,然后对这一个RDD执行了多次算子操作。
/
/
但是要注意到这里为止优化还没有结束,由于rdd1被执行了两次算子操作,第二次执行
reduce
操作的时候,还会再次从源头处重新计算一次rdd1的数据,因此还是会有重复计算的性能开销。
/
/
要彻底解决这个问题,必须结合“原则三:对多次使用的RDD进行持久化”,才能保证一个RDD被多次使用时只被计算一次。
val rdd1
=
sc.textFile(
"hdfs://192.168.0.1:9000/hello.txt"
)
rdd1.
map
(...)
rdd1.
reduce
(...)
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/
/
错误的做法。
/
/
有一个<
Long
, String>格式的RDD,即rdd1。
/
/
接着由于业务需要,对rdd1执行了一个
map
操作,创建了一个rdd2,而rdd2中的数据仅仅是rdd1中的value值而已,也就是说,rdd2是rdd1的子集。
JavaPairRDD<
Long
, String> rdd1
=
...
JavaRDD<String> rdd2
=
rdd1.
map
(...)
/
/
分别对rdd1和rdd2执行了不同的算子操作。
rdd1.reduceByKey(...)
rdd2.
map
(...)
/
/
正确的做法。
/
/
上面这个case中,其实rdd1和rdd2的区别无非就是数据格式不同而已,rdd2的数据完全就是rdd1的子集而已,却创建了两个rdd,并对两个rdd都执行了一次算子操作。
/
/
此时会因为对rdd1执行
map
算子来创建rdd2,而多执行一次算子操作,进而增加性能开销。
/
/
其实在这种情况下完全可以复用同一个RDD。
/
/
我们可以使用rdd1,既做reduceByKey操作,也做
map
操作。
/
/
在进行第二个
map
操作时,只使用每个数据的
tuple
._2,也就是rdd1中的value值,即可。
JavaPairRDD<
Long
, String> rdd1
=
...
rdd1.reduceByKey(...)
rdd1.
map
(
tuple
._2...)
/
/
第二种方式相较于第一种方式而言,很明显减少了一次rdd2的计算开销。
/
/
但是到这里为止,优化还没有结束,对rdd1我们还是执行了两次算子操作,rdd1实际上还是会被计算两次。
/
/
因此还需要配合“原则三:对多次使用的RDD进行持久化”进行使用,才能保证一个RDD被多次使用时只被计算一次。
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
/
/
如果要对一个RDD进行持久化,只要对这个RDD调用cache()和persist()即可。
/
/
正确的做法。
/
/
cache()方法表示:使用非序列化的方式将RDD中的数据全部尝试持久化到内存中。
/
/
此时再对rdd1执行两次算子操作时,只有在第一次执行
map
算子时,才会将这个rdd1从源头处计算一次。
/
/
第二次执行
reduce
算子时,就会直接从内存中提取数据进行计算,不会重复计算一个rdd。
val rdd1
=
sc.textFile(
"hdfs://192.168.0.1:9000/hello.txt"
).cache()
rdd1.
map
(...)
rdd1.
reduce
(...)
/
/
persist()方法表示:手动选择持久化级别,并使用指定的方式进行持久化。
/
/
比如说,StorageLevel.MEMORY_AND_DISK_SER表示,内存充足时优先持久化到内存中,内存不充足时持久化到磁盘文件中。
/
/
而且其中的_SER后缀表示,使用序列化的方式来保存RDD数据,此时RDD中的每个partition都会序列化成一个大的字节数组,然后再持久化到内存或磁盘中。
/
/
序列化的方式可以减少持久化的数据对内存
/
磁盘的占用量,进而避免内存被持久化数据占用过多,从而发生频繁GC。
val rdd1
=
sc.textFile(
"hdfs://192.168.0.1:9000/hello.txt"
).persist(StorageLevel.MEMORY_AND_DISK_SER)
rdd1.
map
(...)
rdd1.
reduce
(...)
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
/
/
传统的join操作会导致shuffle操作。
/
/
因为两个RDD中,相同的key都需要通过网络拉取到一个节点上,由一个task进行join操作。
val rdd3
=
rdd1.join(rdd2)
/
/
Broadcast
+
map
的join操作,不会导致shuffle操作。
/
/
使用Broadcast将一个数据量较小的RDD作为广播变量。
val rdd2Data
=
rdd2.collect()
val rdd2DataBroadcast
=
sc.broadcast(rdd2Data)
/
/
在rdd1.
map
算子中,可以从rdd2DataBroadcast中,获取rdd2的所有数据。
/
/
然后进行遍历,如果发现rdd2中某条数据的key与rdd1的当前数据的key是相同的,那么就判定可以进行join。
/
/
此时就可以根据自己需要的方式,将rdd1当前数据与rdd2中可以连接的数据,拼接在一起(String或
Tuple
)。
val rdd3
=
rdd1.
map
(rdd2DataBroadcast...)
/
/
注意,以上操作,建议仅仅在rdd2的数据量比较少(比如几百M,或者一两G)的情况下使用。
/
/
因为每个Executor的内存中,都会驻留一份rdd2的全量数据。
|
01
02
03
04
05
06
07
08
09
10
11
12
|
/
/
以下代码在算子函数中,使用了外部的变量。
/
/
此时没有做任何特殊操作,每个task都会有一份list1的副本。
val list1
=
...
rdd1.
map
(list1...)
/
/
以下代码将list1封装成了Broadcast类型的广播变量。
/
/
在算子函数中,使用广播变量时,首先会判断当前task所在Executor内存中,是否有变量副本。
/
/
如果有则直接使用;如果没有则从Driver或者其他Executor节点上远程拉取一份放到本地Executor内存中。
/
/
每个Executor内存中,就只会驻留一份广播变量副本。
val list1
=
...
val list1Broadcast
=
sc.broadcast(list1)
rdd1.
map
(list1Broadcast...)
|
1
2
3
4
5
6
|
/
/
创建SparkConf对象。
val conf
=
new SparkConf().setMaster(...).setAppName(...)
/
/
设置序列化器为KryoSerializer。
conf.
set
(
"spark.serializer"
,
"org.apache.spark.serializer.KryoSerializer"
)
/
/
注册要序列化的自定义类型。
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
|
1
2
3
4
5
6
7
8
9
|
./bin/spark-submit \
--master yarn-cluster \
--num-executors 100 \
--executor-memory 6G \
--executor-cores 4 \
--driver-memory 1G \
--conf spark.default.parallelism=1000 \
--conf spark.storage.memoryFraction=0.5 \
--conf spark.shuffle.memoryFraction=0.3 \
|