好吧,让您的数据集稍微有趣一点:
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))
}
由于没有分区器,因此我们的数据集在分区之间均匀分布(Spark中的默认分区方案):
countByPartition(rdd).collect()
Array[Int] = Array(0, 1, 1, 1, 0, 1, 1, 1)
现在让我们重新划分数据集:
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)
请注意,混洗后的值顺序是不确定的。
如果我们使用相同的方式 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))
最后,HashPartitioner(7)
我们得到七个分区,三个非空分区,每个分区有两个元素:
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)
摘要和注释
HashPartitioner
采用单个参数定义分区数
使用hash
键将值分配给分区。hash
函数可能因语言而异(Scala RDD可能使用hashCode
,请DataSets
使用MurmurHash 3,PySpark等portable_hash
)。
在这样简单的情况下,其中key是一个小整数,您可以假定它hash
是一个标识(i = hash(i)
)。
Scala API用于nonNegativeMod
根据计算得出的哈希确定分区,
如果密钥分配不统一,您可能会遇到部分集群空闲的情况
键必须是可哈希的。您可以检查我的答案A列表,作为PySpark的reduceByKey的键,以阅读有关PySpark特定问题的信息。HashPartitioner文档突出了另一个可能的问题:
Java数组具有基于数组标识而不是其内容的hashCode,因此,尝试使用HashPartitioner对RDD [Array [ ]]或RDD [(Array [ ],_)]进行分区将产生意外或错误的结果。
在Python 3中,必须确保哈希是一致的。请参阅异常是什么:应该通过PYTHONHASHSEED在pyspark中禁用字符串哈希的随机性吗?
哈希分区器既不是内射的也不是外射的。可以将多个键分配给单个分区,并且某些分区可以保留为空。
请注意,当与REPL定义的案例类(Apache Spark中的案例类相等)结合使用时,当前基于散列的方法在Scala中不起作用。
HashPartitioner
(或任何其他Partitioner
)对数据进行混洗。除非在多个操作之间重用分区,否则不会减少要重排的数据量。
(1, None)
,hash(2) % P
其中P是分区。不是hash(1) % P
吗?