Spark调优之Cloudera博客(Part 1)

概述

翻译Cloudera关于Spark调优方面的一篇博客How-to: Tune Your Apache Spark Jobs (Part 1),虽然文章写于两年前,但其内容并不过时。

How Spark Executes Your Program

这部分介绍了Spark中的一些基础概念,如driver jobstagetask等,以及Spark的运行机制。充分理解这部分内容是做任何调优的前提,但是我其他的博客进行了介绍,所以略过,感兴趣的同学请查看原文。

Picking the Right Operators

写Spark程序就是运用各种transformation和action完成业务逻辑,使用不同的transformation可以达到相同的效果,但背后的性能可能迥然不同。

Spark中的DataFrameDataSet(以及过时的SchemaRDD)会使用Catalyst优化器优化程序,尽量使用最合适的transformation,这也降低了Spark的使用门槛,即使不熟悉transformation的行为,仍然可以写出性能可以接受的程序。

选用合适的transformation的主要作用在于,尽量避免Shuffle,Shuffle涉及磁盘及网络IO,是昂贵的操作,可能触发Shuffle的操作主要有 repartitionjoincogroup*By*ByKey等。

避免使用groupByKey

rdd.groupByKey().mapValues(_.sum)
rdd.reduceByKey(_ + _)

上面两个代码片段结果一致,但groupByKey进行的是reduce端aggregation,需要将原始数据进行网络传输,reduceByKey会先进行map端aggregation,网络传输的数据会比groupByKey少,因此,如果能够进行map端aggregation的操作,使用reduceByKey

避免使用reduceByKey(当value的输入输出不同时)

val file = sc.textFile(...)
file.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)

上述代码是WordCount的实现,reduceByKey的value输入是int,输出也是int,这是value输入输出相同的例子。

rdd.map(kv => (kv._1, new Set[String]() + kv._2))
    .reduceByKey(_ ++ _)

上述代码,reduceByKey的value输入是String,输出是Set,这是value输入输出不一致的例子。同时,上面的代码会导致过多的Set生成,影响程序效率,可以使用aggregateByKey优化,如下

val zero = new collection.mutable.Set[String]()
rdd.aggregateByKey(zero)(
    (set, v) => set += v,
    (set1, set2) => set1 ++= set2)

避免flatMap-join-groupBy

存在两个已经grouped by key的RDD,想join他们并同时保持grouped,只需使用cogroup,此外,join也是使用cogroup实现。

When Shuffles Don’t Happen

两个RDD执行join操作,会不会发生Shuffle呢?

    val data = sc.parallelize(List("a c", "a b", "b c", "b d", "c d"))
    val wordcount =  data.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _, 2)
    val wordcount2 = data.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _, 2)
    wordcount.join(wordcount2).collect()

上述代码通过reduceByKey参数设置了wordcount wordcount2的Partition数,均为2,其DAG如下
这里写图片描述

    val data = sc.parallelize(List("a c", "a b", "b c", "b d", "c d"))
    val wordcount =  data.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _, 2)
    val wordcount2 = data.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _, 3)
    wordcount.join(wordcount2).collect()

修改wordcount2对应的reduceByKey的参数,Partition设置为3,此时其DAG如下

对比两个DAG,第二个例子仅修改了Partition数,就多产生一次Shuffle,那么join何时需要Shuffle,何时不需要呢?

wordcount、wordcount2的Partition数是否需要Shuffle
相等
不相等

当wordcount、wordcount2的Partition数相等时,只需将其对应Partition聚合就可以,如下
这里写图片描述
当wordcount、wordcount2的Partition数不相等时,需要进行一次Shuffle使两者相等,然后再进行聚合,如下

这也解释了为什么第二个DAG会多一次Shuffle。

When More Shuffles are Better

Shuffle操作是昂贵的,但下面的情形,可以考虑做一下trade off。

  • 提升Partition数,增大并行度。

Secondary Sort

Mapreduce原理中,map端会根据Key的hashCode对Key进行排序,reduce端进行merge-sort使得Key最终有序,可以通过自定义Key的方式控制结果的顺序,这就是二次排序(Secondary Sort),具体参考What is secondary sort in Hadoop, and how does it work?

Spark中不再有二次排序的概念,不同的ShuffleWriter实现行为也不一致,只有SortShuffleWriter会进行map端的sort,并且reduce不会进行merge-sort,下面的issue讨论了reduce端merge-sort的问题,最终因为效率问题不了了之,具体请参考Add MR-style (merge-sort) SortShuffleReader for sort-based shuffle

虽然Spark不再有二次排序,但其有个transformation,repartitionAndSortWithinPartitions ,通过一次Shuffle达到reparation和sortBy的效果,比使用repartition(..).sortBy(…)效率高。

参考:
How-to: Tune Your Apache Spark Jobs (Part 1)
What is secondary sort in Hadoop, and how does it work?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值