HashPartitioner nasıl çalışır?


82

Belgelerini okudum HashPartitioner. Maalesef API çağrıları dışında pek bir şey açıklanmadı. HashPartitionerDağıtılmış kümeyi anahtarların karması temelinde bölümlere ayırdığı varsayımındayım . Örneğin, verilerim şöyle ise

(1,1), (1,2), (1,3), (2,1), (2,2), (2,3)

Böylelikle bölümleyici, bunu aynı bölüme düşen aynı anahtarlarla farklı bölümlere koyacaktır. Ancak yapıcı argümanının önemini anlamıyorum

new HashPartitoner(numPartitions) //What does numPartitions do?

Yukarıdaki veri kümesi için, yapsaydım sonuçlar nasıl değişirdi?

new HashPartitoner(1)
new HashPartitoner(2)
new HashPartitoner(10)

Peki HashPartitioneraslında nasıl çalışıyor?

Yanıtlar:


162

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)

ilk dağıtım

Şimdi veri kümemizi yeniden bölümlere ayıralım:

import org.apache.spark.HashPartitioner
val rddOneP = rdd.partitionBy(new HashPartitioner(1))

Parametre HashPartitionerbö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)

hash-partitioner-1

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 rddanahtar 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ı hashCodemod değerine sahip numPartitionsolduğ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))

hash-partitioner-2

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)

hash-partitioner-7

Özet ve Notlar

  • HashPartitioner bölüm sayısını tanımlayan tek bir argüman alır
  • değerler bölümlere hashanahtarlar kullanılarak atanır . hashişlev dile bağlı olarak değişebilir (Scala RDD kullanabilir hashCode, DataSetsMurmurHash 3, PySpark kullanabilir portable_hash).

    Anahtarın küçük bir tam sayı olduğu böyle basit bir durumda, hashbunun 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.


Harika bir yazı, teşekkürler. Ancak, ben sahip olduğunuz görüntülerdeki fark (1, None)ile hash(2) % PP alanı ise. Olması gerekmiyor hash(1) % Pmu?
javamonkey79

Spark 2.2 kullanıyorum ve partitionByrdd'de api yok . dataframe.write altında bir partitionBy vardır, ancak Partitioner'ı bağımsız değişken olarak almaz.
hakunami

harika yanıt ... karıştırma, bölümleyiciye dayanır, iyi bir bölümleyici, karıştırılan veri miktarını azaltabilir.
Leon

1
Harika 👌 cevap. İnternette sağlam cevap alamadığım bir sorum var. Df.repartition (n) Yani anahtar olarak herhangi bir sütun belirtmediğimizde, hash uygulanacak hiçbir şey olmadığı için hash işçileri dahili olarak nasıl işler?
dsk

@dsk, bir anahtar belirtmezseniz, repartition'ın RoundRobinPartitioning kullandığına inanıyorum. Burada biraz tartışma .
Mike Souder

6

RDDdağıtılır, bu, birkaç parçaya bölündüğü anlamına gelir. Bu bölümlerin her biri potansiyel olarak farklı bir makinededir. Bağımsız değişkenli Hash partitioner numPartitions, hangi partisyonun (key, value)aşağıdaki şekilde yerleştirileceğini seçer :

  1. Tam olarak numPartitionsbölümler oluşturur .
  2. Numaralı (key, value)bölümdeki yerlerHash(key) % numPartitions

3

HashPartitioner.getPartitionYöntem alır anahtarı onun argüman olarak ve döner endeksi anahtarın ait bölümünün. Bölümleyici, geçerli endekslerin ne olduğunu bilmek zorundadır, bu nedenle doğru aralıktaki sayıları döndürür. Bölüm sayısı,numPartitions yapıcı bağımsız değişkeni .

Uygulama kabaca geri dönüyor key.hashCode() % numPartitions. Daha fazla ayrıntı için Partitioner.scala'ya bakın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.