Peki, veri kümenizi marjinal olarak daha ilginç hale getirelim:
val rdd = sc.parallelize(for {
x <- 1 to 3
y <- 1 to 2
} yield (x, None), 8)
Altı unsurumuz var:
rdd.count
Long = 6
bölümleyici yok:
rdd.partitioner
Option[org.apache.spark.Partitioner] = None
ve sekiz bölüm:
rdd.partitions.length
Int = 8
Şimdi bölüm başına eleman sayısını saymak için küçük bir yardımcı tanımlayalım:
import org.apache.spark.rdd.RDD
def countByPartition(rdd: RDD[(Int, None.type)]) = {
rdd.mapPartitions(iter => Iterator(iter.length))
}
Bölümleyicimiz olmadığından, veri kümemiz bölümler arasında tek tip olarak dağıtılır (Spark'ta Varsayılan Bölümleme Şeması ):
countByPartition(rdd).collect()
Array[Int] = Array(0, 1, 1, 1, 0, 1, 1, 1)
Şimdi veri kümemizi yeniden bölümlere ayıralım:
import org.apache.spark.HashPartitioner
val rddOneP = rdd.partitionBy(new HashPartitioner(1))
Parametre HashPartitioner
bölümlerin sayısını tanımladığından, bir bölüm bekliyoruz:
rddOneP.partitions.length
Int = 1
Sadece bir bölümümüz olduğu için tüm öğeleri içerir:
countByPartition(rddOneP).collect
Array[Int] = Array(6)
Karıştırmadan sonraki değerlerin sırasının deterministik olmadığını unutmayın.
Aynı şekilde kullanırsak HashPartitioner(2)
val rddTwoP = rdd.partitionBy(new HashPartitioner(2))
2 bölüm alacağız:
rddTwoP.partitions.length
Int = 2
Yana rdd
anahtar verilerine göre paylaştırılır artık eşit dağılmış olmayacak:
countByPartition(rddTwoP).collect()
Array[Int] = Array(2, 4)
Çünkü üç anahtara ve sadece iki farklı hashCode
mod değerine sahip numPartitions
olduğu için burada beklenmedik bir şey yoktur:
(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))
Sadece yukarıdakileri onaylamak için:
rddTwoP.mapPartitions(iter => Iterator(iter.map(_._1).toSet)).collect()
Array[scala.collection.immutable.Set[Int]] = Array(Set(2), Set(1, 3))
Son olarak, HashPartitioner(7)
her biri 2 öğeli üç boş olmayan yedi bölüm elde ederiz:
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)
Özet ve Notlar
HashPartitioner
bölüm sayısını tanımlayan tek bir argüman alır
değerler bölümlere hash
anahtarlar kullanılarak atanır . hash
işlev dile bağlı olarak değişebilir (Scala RDD kullanabilir hashCode
, DataSets
MurmurHash 3, PySpark kullanabilir portable_hash
).
Anahtarın küçük bir tam sayı olduğu böyle basit bir durumda, hash
bunun bir kimlik ( i = hash(i)
) olduğunu varsayabilirsiniz .
Scala API nonNegativeMod
, hesaplanan hash'e göre bölümü belirlemek için kullanır ,
Anahtarların dağıtımı tek tip değilse, kümenizin bir kısmının boşta kaldığı durumlarda sonuçlanabilirsiniz
anahtarların hashable olması gerekir. PySpark'a özgü sorunlar hakkında bilgi almak için PySpark'ın lessByKey'in anahtarı için A listesi cevabımı kontrol edebilirsiniz . Bir başka olası sorun HashPartitioner belgelerinde vurgulanmaktadır :
Java dizileri, dizilerin içeriklerinden ziyade kimliklerine dayanan hashCode'lara sahiptir, bu nedenle bir HashPartitioner kullanarak bir RDD [Dizi [ ]] veya RDD [(Dizi [ ], _)] 'yi bölümlemeye çalışmak, beklenmeyen veya yanlış bir sonuç üretecektir.
Python 3'te hashing'in tutarlı olduğundan emin olmalısınız. Bkz. İstisna: Dize karmasının rasgeleliği, pyspark'ta PYTHONHASHSEED aracılığıyla devre dışı bırakılmalıdır.
Hash partitioner ne enjekte ne de kapsayıcıdır. Tek bir bölüme birden çok anahtar atanabilir ve bazı bölümler boş kalabilir.
Lütfen şu anda karma tabanlı yöntemlerin, REPL tanımlı vaka sınıflarıyla ( Apache Spark'ta Case sınıfı eşitliği ) birleştirildiğinde Scala'da çalışmadığını unutmayın .
HashPartitioner
(veya başka herhangi biri Partitioner
) verileri karıştırır. Bölümleme, birden çok işlem arasında yeniden kullanılmadığı sürece, karıştırılacak veri miktarını azaltmaz.
(1, None)
ilehash(2) % P
P alanı ise. Olması gerekmiyorhash(1) % P
mu?