Що ж, давайте зробимо ваш набір даних набагато цікавішим:
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
мода, 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)
ми отримуємо сім розділів, три непусті з 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)
Короткий зміст та примітки
HashPartitioner
приймає один аргумент, який визначає кількість розділів
значення присвоюються розділам за допомогою hash
клавіш. hash
Функція може відрізнятися залежно від мови (може використовувати Scala RDD hashCode
, DataSets
використовувати MurmurHash 3, PySpark, portable_hash
).
У такому простому випадку, коли ключ є малим цілим числом, можна припустити, що hash
це ідентичність ( i = hash(i)
).
API Scala використовує nonNegativeMod
для визначення розділу на основі обчисленого хешу,
якщо розподіл ключів неоднаковий, ви можете потрапити в ситуації, коли частина кластера не працює
ключі повинні бути розмитими. Ви можете перевірити мою відповідь на список як ключ для PySpark's reduceByKey, щоб прочитати про конкретні проблеми PySpark. Інша можлива проблема висвітлена в документації HashPartitioner :
Масиви Java мають хеш-коди, які базуються на ідентифікаціях масивів, а не на їх вмісті, тому спроба розділити RDD [Array [ ]] або RDD [(Array [ ], _)] за допомогою HashPartitioner дасть несподіваний або неправильний результат.
У Python 3 ви повинні переконатися, що хешування є послідовним. Дивіться, що означає виняток: випадковість хешу рядків слід вимкнути за допомогою PYTHONHASHSEED у pyspark?
Розділювач хешів не є ні ін’єктивним, ні сюр’єктивним. Одному розділу можна призначити кілька ключів, а деякі розділи можуть залишатися порожніми.
Зверніть увагу, що в даний час методи, засновані на хеші, не працюють у Scala в поєднанні з визначеними REPL класами випадків ( рівність класу Case в Apache Spark ).
HashPartitioner
(або будь-який інший Partitioner
) перетасовує дані. Якщо розділення не використовується повторно між кількома операціями, це не зменшує обсяг даних, що підлягають перетасовці.
(1, None)
зhash(2) % P
де P є розділом. Чи не повинно бутиhash(1) % P
?