partition是spark rdd計算的最小單元。爲什麼是最小單元?先從分佈式說起,分佈式計算的特點就是批處理,將大量的數據分成若干批次,使得利用廉價機器搭建的集羣也可以完成海量數據的計算。大量的數據分散在集羣中的若干節點上,每個節點上的那部分數據,在執行計算的時候,又可以切分成若干份,每一份就是一個批次,也就是一個partition。
spark計算的性能與partition的數量有很大的關係。每個task處理一個partition。如果partition的數量小於集羣可用的CPU核數,就不能充分利用並行計算的性能,還可能引發數據傾斜的問題。但是partition的數量遠大於集羣可用CPU核數的時候,又可能導致資源分配的時間超過任務實際執行的時間。那麼partition的數量又是由什麼決定的呢?
備註: 這裏提到的相關API及代碼都是基於PySpark
一、數據源
rdd如果是從某些數據源創建的,則partition的數量與這些數據源有直接關係。下文的sc
指SparkContext實例, spark
指的是SparkSession實例。
從內存中創建
sc.parallelize(...)
這種方式創建的partition數量等於SparkContext的defaultParallelism的值,或者等於傳給parallelize的numSlices參數。
讀取HDFS文件創建
包括使用sc.textFile(...)
或spark.sql(...)
查詢Hive表得到的結果的partition數量等於HDFS中的文件塊數。
二、通過計算
通過Generic Transformation創建
定義
rdd_size = rdd.getNumPartitions()
other_rdd_size = rdd.getNumPartitions()
filter(), map(), flatMap(), distinct()
partition數量等於parent RDD的數量。rdd.union(other_rdd)
partition數量等於rdd_size + other_rdd_size
rdd.intersection(other_rdd)
partition數量等於max(rdd_size, other_rdd_size)
rdd.subtract(other_rdd)
partition數量等於rdd_size
rdd.cartesian(other_rdd)
partition數量等於rdd_size * other_rdd_size
通過Key-based Transformation創建
什麼是key-based?key-based指的是執行transformation的rdd都必須是pair rdd,在pyspark中,並沒有pair rdd這個類型,一個rdd要成爲有效的pair rdd, 只需要rdd中的每條記錄滿足k, v = kv
,也就是只要是一個支持迭代,且一共含有兩個元素的對象即可,可以是(k, v)
,也可以是[k, v]
, 也可以是自定義類型等。
在默認的情況下:
reduceByKey(), foldByKey(), combineByKey(), groupByKey()
partition數量等於parent rdd的數量, 使用HashPartitioner
,允許自己指定hash函數sortByKey()
partition數量等於parent rdd的數量,使用RangePartitioner
mapValues(), flatMapValues()
partition數量等於parent rdd的數量,使用parent rdd的partitioner,這兩個函數接收的parent rdd必須爲pair rdd, 確保執行完以後保持原有partition不變join(), leftOuterJoin(), rightOuterJoin()
partition數量取決於相關的兩個rdd, 使用HashPartitioner
三、自定義partition數量
在使用HashPartitioner的transformation的函數中,可以通過numPartitions與partitionFunc改變默認的partition數量以及數據的分佈方式。
numPartitions指定結果rdd的partition數量, partitionFunc只要是一個返回一個int數值的函數即可,它決定了一條記錄將要被分配的partition的index
# pyspark 源碼
buckets[partitionFunc(k) % numPartitions].append((k, v))
幾個幫助瞭解partition的API
# 查看partition數量
rdd.getNumPartitions()
# 查看所用partitioner
rdd.partitioner
# 查看具體數據在每個partition的分佈
rdd.glom().collect() # 僅適用小量數據, 數據量大慎用
在做試驗的時候, 通過rdd.glom().collect()
可以看到數據在每個partition的分佈情況,有的時候可以看出, 當partition數量多於數據條數,或是數據傾斜的時候,有的partition是空的,所以spark不會因爲某個partition是空的就移除它,而每個partition又對應一個task,爲空的partition啓停任務, 必然引起不必要的資源消耗而影響性能。 所以分配得當的partition數量是非常重要的。