RDD算子中分区数(并行度)的调整
生产+面试。
下面①②③:
①是textFile和reduceByKey的分区(并行度)都是默认分区数;
②textFile分区数修改成1,reduceByKey不变
③textFile分区数修改成1,reduceByKey修改成4
以上这些在生产上调优很重要。
①
spark.sparkContext.textFile("file:///home/hadoop/data/wordcount.txt")
.flatMap(_.split("\t"))
.map((_,1))
.reduceByKey(_+_)
.collect
//源码
//可以看出来textFile中有两个参数,第一个参数是路径,第二个参数是分区数,不写就是默认分区数
def textFile(
path: String,
minPartitions: Int = defaultMinPartitions): RDD[String] = withScope {
`....................
//默认最小分区数是(默认最小平行度和2)的最小值
//默认最小并行度是totalCoreCount
def defaultMinPartitions: Int = math.min(defaultParallelism, 2)
def defaultParallelism: Int = {
assertNotStopped()
taskScheduler.defaultParallelism
}
//默认最小并行度是Core的总数量和2的最大值
override def defaultParallelism(): Int = {
conf.getInt("spark.default.parallelism", math.max(totalCoreCount.get(), 2))
}
//spark-shell --master local[1] 这样task数是1
//spark-shell --master local[2] 这样task数是2
//spark-shell --master local[4] 大于2的,这样task数还是2
②
scala> spark.sparkContext.textFile("file:///home/hadoop/data/wordcount.txt",1)
.flatMap(_.split("\t"))
.map((_,1))
.reduceByKey(_+_)
.collect
res5: Array[(String, Int)] = Array((us,1), (room,2), (word,1), (china,3), (computer,1))
③
scala> spark.sparkContext.textFile("file:///home/hadoop/data/wordcount.txt",1)
.flatMap(_.split("\t"))
.map((_,1))
.reduceByKey(_+_,4)
.collect
res6: Array[(String, Int)] = Array((us,1), (word,1), (room,2), (china,3), (computer,1))
shuffle前,textFile把并行度变成了1,reduceByKey并行度变成了4。
总结:
RDD操作过程中,遇到shuffle就会产生stage,以上例子有一个shuffle,划分为两个stage,shuffle后面的stage叫做ResultStage,或者FinalStage。前面的stage叫做ShuffleMapStage。这两个stage的并行度是可以自己去调整的,这个在生产上调优分厂重要的。
- map端,输入文件,hdfs上有几个block块,就有几个partition,就有几个task并行度。
你要map读文件读的快,你可以把map的并行度提高, - reduce端,如果你觉得跑的太慢,可以在reduce端加并行度,比如:reduceByKey(fun,4),4个并行度。
- ResultStage(也就是reduce端)若未指定Patition数,则默认和ShuffleMapStage(也就是map端)最后RDD的分区数(并行度)保持一致。如果指定了分区数,就以指定的为准。
ShuffleManager
生产+面试。
源码
可将spark的代码调至1.6版本(pom.xml文件修改一下版本),查看SparkEnv.class文件,搜索一下shuffleManager。
看一下Spark1.6版本的源码:
// Let the user specify short names for shuffle managers
val shortShuffleMgrNames = Map(
"hash" -> "org.apache.spark.shuffle.hash.HashShuffleManager",
"sort" -> "org.apache.spark.shuffle.sort.SortShuffleManager",
"tungsten-sort" -> "org.apache.spark.shuffle.sort.SortShuffleManager")
val shuffleMgrName = conf.get("spark.shuffle.manager", "sort") //这个默认是sort(1.2版本之前是hash)
val shuffleMgrClass = shortShuffleMgrNames.getOrElse(shuffleMgrName.toLowerCase, shuffleMgrName)
val shuffleManager = instantiateClass[ShuffleManager](shuffleMgrClass)
//instantiateClass这个是什么???
//包名加类名去查找,底层走的是Class.forName
看一下Spark2.4.0版本的源码:
// Let the user specify short names for shuffle managers
val shortShuffleMgrNames = Map(
"sort" -> classOf[org.apache.spark.shuffle.sort.SortShuffleManager].getName,
"tungsten-sort" -> classOf[org.apache.spark.shuffle.sort.SortShuffleManager].getName)
val shuffleMgrName = conf.get("spark.shuffle.manager", "sort") //这个默认是sort
val shuffleMgrClass =
shortShuffleMgrNames.getOrElse(shuffleMgrName.toLowerCase(Locale.ROOT), shuffleMgrName)
val shuffleManager = instantiateClass[ShuffleManager](shuffleMgrClass)
Spark2.4.0版本里已经没有"hash",已经被移除。但是HashShuffleManager虽然被移除,面试的时候还是要被问的,比如HashShuffleManager什么原理。
Spark1.2之前默认使用的是HashShuffleManager、1.2和1.2版本之后默认使用SortShuffleManager,2.0之后HashShuffleManager被废除。
HashShuffleManager架构解析
maptask理解为:ShuffleMapStage
reducetask理解为:ResultStage,或者FinalStage
前提条件:现在有一台机器,2个core,那么并行度就是2。如果现在maptask有4个,reducetask有2个。
分析:
-
4个maptask,2个core,要跑两轮,才能把任务跑完。
-
shuffle:把相同的key的数据分发到一个地方。
-
每个bucket桶的大小默认为 :spark.shuffle.file.buffer:32k
这个参数在生产太小,肯定要进行调整。 在map task处理的数据量比较大的情况下,而你的task的内存缓冲默认是比较小的,32kb。可能会造成多次的map端往磁盘文件的spill溢写操作,发生大量的磁盘IO,从而降低性能。
-
每个maptask都会生成n个reducetask的文件。**如果maptask的数量为m,reducetask的数量为r,那么它就会生成 m*r 个文件。**生成的这些文件,先写到内存中(一个个小的bucket),然后再写到本地磁盘(默认路径:spark.local.dir=/tmp)。生产上,挂多个磁盘,多个目录之间以逗号分隔,这样可以写的更快,而且不至于写一个磁盘导致系统挂掉。
启动的bucket桶的数量为:core的数量 * r
(如上图,要跑两轮,第一轮跑的时候,第二轮还没开始,第二轮的bucket还没有)
假如现在有8个core,1000个task,那么需要为bucket开辟的内存为:8*1000*32k=256M
加入现在有100个executor,每个executor2个core,总共有1000个maptask,1000个reducetask。那么要输出的文件数为:1000*1000=100万个临时文件。这个在生产上是很致命的,肯定不行。 -
然后各个reducetask再去上面各个文件中去拉数据,拉来的数据也要先放在一个缓存buffer里去接收它。这个缓存buffer大小就是spark.reducer.maxSizeInFlight。
每个reduce任务获取的map输出的最大大小spark.reducer.maxSizeInFlight:48M
(这个参数一般可以调为2倍,96M)
总结三个问题:(最主要是文件数太多这个问题)
①一个是maptask产生的文件数太多;一、将临时文件合并:spark.shuffle.consolidateFiles=true,默认是false,此时生成的文件数是 Core*R,但是当R很大时依旧会崩溃。二、写到本地磁盘(默认路径:spark.local.dir=/tmp)。生产上,挂多个磁盘,多个目录之间以逗号分隔,这样可以写的更快,而且不至于写一个磁盘导致系统挂掉。
②缓冲区的buffer太小,需要频繁的写磁盘,spark.shuffle.file.buffer这个参数需要调整,这个要调大很多。
③reducetask从map拉来的数据先放在一个缓存buffer里去接收它,这个buffer大小spark.reducer.maxSizeInFlight:48M,一般可以调整为2倍。
可参考:https://blog.csdn.net/tian_qing_lei/article/details/78002395