Apache Spark连接操作的弱扩展性差

时间:2017-10-04 14:34:25

标签: performance scala apache-spark distributed-computing

我运行"加入"在Apache Spark上运行,看看没有弱的可扩展性。如果有人能够解释这一点,将不胜感激。

我创建了两个数据帧(" a"," b")和(" a"," c")并加入数据帧在第一栏。我为&#34生成数据帧值;一对一"加入。另外,我使用相同的分区来避免随机播放。

数据帧中的行数 - 1024 * 1024 * 16 * cores_total(cores_total - 启动程序的核心总数)。 专栏" a"由随机的Int值组成," b"的所有值都包含在内。列等于1," c"的所有值列等于2.

理论上,随着数据大小和核心数量增加64倍,执行时间应保持不变,但执行时间会略有增加。我获得了以下执行时间:

enter image description here

Apache Spark版本 - 2.1.0。我们使用8个集群节点,配备1 Gbit以太网,每个节点有2个Intel Xeon E5-2630,64 GB RAM。

/* join perf */
import scala.io.Source
import scala.math._
import org.apache.spark._
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.SparkConf
import scala.util.control.Breaks._
import scala.collection.mutable._
import org.apache.spark.rdd._
import org.apache.spark.sql._
import scala.util.Random
import org.apache.spark.util.SizeEstimator
import org.apache.spark.HashPartitioner

object joinPerf {

    def get_array(n: Int): Array[Int] = {
        var res = Array[Int]()
        for (x <- 1 to n) {
            res :+= Random.nextInt
        }

        return res
    }

    def main(args: Array[String]) {
        val start_time = System.nanoTime
        val conf = new SparkConf().setAppName("joinPerf")
        val sc = new SparkContext(conf)
        val cores_total = sc.getConf.get("spark.cores.max").toInt
        val partitions_total = sc.getConf.get("spark.default.parallelism").toInt
        val sqlContext = new org.apache.spark.sql.SQLContext(sc)
        import sqlContext._
        import sqlContext.implicits._
        println("start")
        val elems_total = 1024 * 1024 * 16 * cores_total
        val start_cnt = 1024 * 1024
        Random.setSeed(785354)

        var vals = Vector[Int]()
        for (x <- 1 to start_cnt) {
            vals :+= Random.nextInt
        }

        var test_rdd = sc.parallelize(vals)
        println(test_rdd.count)
        test_rdd = test_rdd.flatMap(x => get_array(elems_total / start_cnt)).distinct

        println("test_rdd count = " + test_rdd.count)
        println("partitions count = " + test_rdd.getNumPartitions)

        var test_rdd1 = test_rdd.map(x => (x, 1)).toDF("a", "b").repartition(partitions_total, $"a").cache
        var test_rdd2 = test_rdd.map(x => (x, 2)).toDF("a", "c").repartition(partitions_total, $"a").cache

        println("test_rdd1 count = " + test_rdd1.count)
        println("test_rdd2 count = " + test_rdd2.count)

        var start_test_time = System.nanoTime
        var test_res = test_rdd1.join(test_rdd2, test_rdd1("a") === test_rdd2("a"))
        println(test_res.count)
        print("join time = ")
        println((System.nanoTime - start_test_time) / 1e9d + " sec. ")

        print("all time = ")
        println((System.nanoTime - start_time) / 1e9d + " sec. ")
        sc.stop()
    }
}

配置参数:

spark.serializer                 org.apache.spark.serializer.KryoSerializer
spark.kryoserializer.buffer.max  1024
spark.kryo.unsafe                true
spark.kryo.referenceTracking     false
spark.driver.memory              22g
spark.executor.memory            22g
spark.driver.maxResultSize       22g
spark.rpc.message.maxSize        2047
spark.memory.fraction            0.8
spark.memory.storageFraction     0.5
spark.executor.extraJavaOptions  "-XX:+UseParallelGC"

每个核心的分区 - 4.

启动程序示例:

./bin/spark-submit --class "joinPerf" --conf spark.executor.cores=8 --conf spark.cores.max=64 --conf spark.default.parallelism=256 ./joinPerf.jar

1 个答案:

答案 0 :(得分:1)

  

理论上,随着数据大小和核心数量增加64倍,执行时间应保持不变,但执行时间会略有增长

不应该。虽然可以预期线性可扩展性,假设没有IO瓶颈,但在对均匀分布的数据执行严格的本地操作时,当转换需要数据交换时,情况就不再如此(RDD shuffles,Dataset {{1 }})。在广泛的转换中,Exchange属于最昂贵的类别(下一个joins - 类似的操作),因为它们具有非还原性质,并且使用大型本地支持集合。

Shuffles不仅具有高于线性的复杂度(对于基于排序的方法至少 O(N log N )),而且还可能导致数据的非均匀分布,并且需要大量的磁盘和网络IO。

如果您的代码将数据混洗两次,则会更加严重 - 一次重新分区groupByKey一次,RDDs join Datasets {{1} }}与HashPartitioner分区不兼容。)

最后增加群集大小,有其自身的性能影响,与增加的通信和同步开销以及减少的数据局部性有关。

总的来说,您很少会看到真正的线性可扩展性,即使您这样做,也可以预期斜率<&lt; 1。

旁注在使用RDDs时,我不会依赖Dataset - cache惯用语。 It is likely to be unreliable

另见Spark: Inconsistent performance number in scaling number of cores