前言
分区只不过是将原来大的数据分成几部分。 比如分布式系统中的分区,我们可以将其定义为大型数据集的分区,并将它们存储为整个群集中的多个部分。
通过分区可以减少网络I/O,从而可以更快地处理数据。在Spark中,co-group
,groupBy
,groupByKey
等操作需要大量的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)