【Spark】Spark中的Partitioner

前言

分区只不过是将原来大的数据分成几部分。 比如分布式系统中的分区,我们可以将其定义为大型数据集的分区,并将它们存储为整个群集中的多个部分。
通过分区可以减少网络I/O,从而可以更快地处理数据。在Spark中,co-groupgroupBygroupByKey等操作需要大量的I/O操作。 在这种情况下,如果我们应用分区,那么我们可以快速减少I/O操作的数量,以便我们可以加速数据处理。
Spark适用于数据局部性原则。 工作节点获取更接近它们的处理数据。 通过分区网络,I/O将减少,从而可以更快地处理数据。
同时,Spark可以为RDD的每个分区运行1个并发任务(最多为集群中的核心数)。

Spark有两种类型的分区技术。 一个是HashPartitioner,另一个是RangePartitioner

分区器Partitioner

key-value pair RDD中的元素是按key的值分进行分区的。
每个key都会被映射到对应的分区ID,从0到numPartitions - 1

分区程序必须是确定性的,即它必须在给定相同分区键的情况下返回相同的分区ID。

如果设置了spark.default.parallelism,我们将使用SparkContext defaultParallelism的值作为默认分区号,否则我们将使用上游分区的最大数量。

一般来说,我们尽量从rdds中选择具有最大分区数的分区程序。 如果此分区符合条件(rdds中最大分区数量级别内的分区数),或者分区数大于或等于默认分区数,则使用此分区程序。
否则,我们将使用具有默认分区数的新HashPartitioner。

除非设置了spark.default.parallelism,否则分区数将与最大上游RDD中的分区数相同,因为这种配置最保险, 可以避免导致内存不足。

HashPartitioner

HashPartitioner,它使用Java的Object.hashCode实现基于散列的分区。
hashcode()的概念是相等的对象应该具有相同的哈希码。 因此,基于此hashcode()概念,HashPartitioner将划分具有相同hashcode()的键到同一分区。

HashPartitioner是Spark的默认分区程序。 如果我们没有配置任何分区器,那么Spark将使用这个散列分区器来对数据进去分区。

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

scala中的示例:


scala> import org.apache.spark.HashPartitioner
scala> val textFile = sc.textFile("file:///Users/lestat/Desktop/test.txt")
textFile: org.apache.spark.rdd.RDD[String] = file:/Users/lestat/Desktop/test.txt MapPartitionsRDD[13] at textFile at <console>:29
scala> val counts = textFile.flatMap(line => line.split(",")).map(word => (word, 1)).partitionBy(new HashPartitioner(10))
counts: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[16] at partitionBy at <console>:30
scala> counts.reduceByKey(_+_).saveAsTextFile("/home/kiran/partition_spark/hash")
scala>

结果如下,生成了10个part文件。

RangePartitioner

RangePartitioner按范围将 可排序记录 划分为大致相等的范围。 范围通过对传入的RDD的内容进行采样来确定。即RangePartitioner将根据Key对记录进行排序,然后根据给定的值将记录划分为多个分区。
注意,在采样记录数小于partitions的情况下,RangePartitioner创建的实际分区数可能与partitions参数不同。


scala> import org.apache.spark.RangePartitioner
import org.apache.spark.RangePartitioner
scala> val textFile = sc.textFile("file:///Users/lestat/Desktop/test.txt")
textFile: org.apache.spark.rdd.RDD[String] = file:///Users/lestat/Desktop/test.txt MapPartitionsRDD[5] at textFile at <console>:27
scala> val counts = textFile.flatMap(line => line.split(",")).map(word => (word, 1))
counts: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[7] at map at <console>:28
scala> val range = counts.partitionBy(new RangePartitioner(4,counts))
range: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[10] at partitionBy at <console>:28
scala> range.reduceByKey(_+_).saveAsTextFile("/Users/lestat/Desktop/range")

结果如下,生成了4个part文件(均匀条数)。

lestat@Lestats-MBP:~/Desktop/range$ cat part-00000
(2,1)
(1,1)
(11,1)
(10,1)
lestat@Lestats-MBP:~/Desktop/range$ cat part-00001
(253,1)
(4,1)
(234,1)
(3,1)
lestat@Lestats-MBP:~/Desktop/range$ cat part-00002
(8,1)
(5,1)
(6,2)
lestat@Lestats-MBP:~/Desktop/range$ cat part-00003
(d,1)
(a,1)
(b,1)
(r,1)

CustomPartitioner

我们还可以通过扩展Spark中的默认Partitioner类来自定义我们需要的分区数以及应该存储在这些分区中的内容。然后通过partitionBy()在RDD上应用自定义分区逻辑。

注意
使用partitionBy()的RDD必须包含键值对元组对象。

一个简单的pyspark例子:

#In Python
def my_partitioner(country):
    return hash(country)
rdd = sc.parallelize(transactions) \
        .map(lambda el: (el['country'], el)) \
        .partitionBy(4, my_partitioner)

发布了94 篇原创文章 · 获赞 110 · 访问量 5051

猜你喜欢

转载自blog.csdn.net/beautiful_huang/article/details/103832822