HashPartitioner如何运作?

时间:2015-07-15 07:46:38

标签: scala apache-spark rdd partitioning

我阅读了HashPartitioner的文档。不幸的是,除了API调用之外没有解释太多。我假设MyModel.findWithIncrement({name: "someName"}, function (err, result) { }) 基于密钥的散列对分布式集进行分区。例如,如果我的数据是

HashPartitioner

因此,分区程序会将其放入不同的分区,同一个键落在同一个分区中。但是我不明白构造函数参数的重要性

(1,1), (1,2), (1,3), (2,1), (2,2), (2,3)

对于上述数据集,如果我做了

,结果会有何不同
new HashPartitoner(numPartitions) //What does numPartitions do?

那么new HashPartitoner(1) new HashPartitoner(2) new HashPartitoner(10) 如何实际运作?

1 个答案:

答案 0 :(得分:129)

好吧,让我们让你的数据集更有趣:

val rdd = sc.parallelize(for {
    x <- 1 to 3
    y <- 1 to 2
} yield (x, None), 8)

我们有六个要素:

rdd.count
Long = 6

没有分区:

rdd.partitioner
Option[org.apache.spark.Partitioner] = None

和八个分区:

rdd.partitions.length
Int = 8

现在让我们定义小助手来计算每个分区的元素数量:

import org.apache.spark.rdd.RDD

def countByPartition(rdd: RDD[(Int, None.type)]) = {
    rdd.mapPartitions(iter => Iterator(iter.length))
}

由于我们没有分区程序,因此我们的数据集在分区(Default Partitioning Scheme in Spark)之间统一分配:

countByPartition(rdd).collect()
Array[Int] = Array(0, 1, 1, 1, 0, 1, 1, 1)

inital-distribution

现在让我们重新分区我们的数据集:

import org.apache.spark.HashPartitioner
val rddOneP = rdd.partitionBy(new HashPartitioner(1))

由于传递给HashPartitioner的参数定义了我们期望一个分区的分区数:

rddOneP.partitions.length
Int = 1

由于我们只有一个分区,因此它包含所有元素:

countByPartition(rddOneP).collect
Array[Int] = Array(6)

hash-partitioner-1

请注意,shuffle之后的值的顺序是不确定的。

如果我们使用HashPartitioner(2)

,也一样
val rddTwoP = rdd.partitionBy(new HashPartitioner(2))

我们将获得2个分区:

rddTwoP.partitions.length
Int = 2

由于rdd按关键数据分区,因此不再统一分发:

countByPartition(rddTwoP).collect()
Array[Int] = Array(2, 4)

因为有三个键且只有两个不同的hashCode mod numPartitions值,所以没有任何意外:

(1 to 3).map((k: Int) => (k, k.hashCode, k.hashCode % 2))
scala.collection.immutable.IndexedSeq[(Int, Int, Int)] = Vector((1,1,1), (2,2,0), (3,3,1))

只是为了证实以上内容:

rddTwoP.mapPartitions(iter => Iterator(iter.map(_._1).toSet)).collect()
Array[scala.collection.immutable.Set[Int]] = Array(Set(2), Set(1, 3))

hash-partitioner-2

最后使用HashPartitioner(7)我们得到七个分区,三个非空,每个分区包含2个元素:

val rddSevenP = rdd.partitionBy(new HashPartitioner(7))
rddSevenP.partitions.length
Int = 7
countByPartition(rddTenP).collect()
Array[Int] = Array(0, 2, 2, 2, 0, 0, 0)

hash-partitioner-7

摘要和注释

  • HashPartitioner采用一个定义分区数的参数
  • 使用hash个键将值分配给分区。 hash函数可能因语言而异(Scala RDD可能使用hashCodeDataSets使用MurmurHash 3,PySpark,portable_hash)。

    在这种简单的情况下,key是一个小整数,你可以假设hash是一个身份(i = hash(i))。

    Scala API使用nonNegativeMod来确定基于计算哈希的分区,

  • 如果密钥分配不统一,您可能会在群集的一部分空闲时出现

  • 键必须是可清洗的。您可以查看我对A list as a key for PySpark's reduceByKey的回答,了解有关PySpark特定问题的信息。 HashPartitioner documentation强调了另一个可能的问题:

      

    Java数组具有基于数组的hashCodes&#39;身份而不是其内容,因此尝试使用HashPartitioner对RDD [Array []]或RDD [(Array [],_)]进行分区将产生意外或不正确的结果。

  • 在Python 3中,您必须确保散列是一致的。请参阅What does Exception: Randomness of hash of string should be disabled via PYTHONHASHSEED mean in pyspark?

  • 哈希分区既不是单射的也不是满足的。可以将多个密钥分配给单个分区,并且某些分区可以保持为空。

  • 请注意,当与REPL定义的案例类(Case class equality in Apache Spark)结合使用时,当前基于散列的方法在Scala中不起作用。

  • HashPartitioner(或任何其他Partitioner)随机播放数据。除非在多个操作之间重用分区,否则它不会减少要洗牌的数据量。