HashPartitioner hoạt động như thế nào?


82

Tôi đọc trên tài liệu của HashPartitioner. Thật không may, không có gì được giải thích nhiều ngoại trừ các lệnh gọi API. Tôi đang giả định rằng HashPartitionerphân vùng tập hợp phân tán dựa trên băm của các khóa. Ví dụ: nếu dữ liệu của tôi giống như

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

Vì vậy, trình phân vùng sẽ đặt nó vào các phân vùng khác nhau với các khóa giống nhau nằm trong cùng một phân vùng. Tuy nhiên tôi không hiểu ý nghĩa của đối số hàm tạo

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

Đối với tập dữ liệu trên, kết quả sẽ khác như thế nào nếu tôi làm như vậy

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

Vậy HashPartitionerthực tế hoạt động như thế nào ?

Câu trả lời:


161

Chà, hãy làm cho tập dữ liệu của bạn thú vị hơn một chút:

val rdd = sc.parallelize(for {
    x <- 1 to 3
    y <- 1 to 2
} yield (x, None), 8)

Chúng tôi có sáu yếu tố:

rdd.count
Long = 6

không có phân vùng:

rdd.partitioner
Option[org.apache.spark.Partitioner] = None

và tám phân vùng:

rdd.partitions.length
Int = 8

Bây giờ, hãy xác định trình trợ giúp nhỏ để đếm số phần tử trên mỗi phân vùng:

import org.apache.spark.rdd.RDD

def countByPartition(rdd: RDD[(Int, None.type)]) = {
    rdd.mapPartitions(iter => Iterator(iter.length))
}

Vì chúng tôi không có trình phân vùng nên tập dữ liệu của chúng tôi được phân phối đồng nhất giữa các phân vùng ( Sơ đồ phân vùng mặc định trong Spark ):

countByPartition(rdd).collect()
Array[Int] = Array(0, 1, 1, 1, 0, 1, 1, 1)

i sinh dục-phân phối

Bây giờ hãy phân vùng lại tập dữ liệu của chúng tôi:

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

Vì tham số được truyền để HashPartitionerxác định số lượng phân vùng, chúng tôi mong đợi một phân vùng:

rddOneP.partitions.length
Int = 1

Vì chúng ta chỉ có một phân vùng nên nó chứa tất cả các phần tử:

countByPartition(rddOneP).collect
Array[Int] = Array(6)

băm-phân vùng-1

Lưu ý rằng thứ tự của các giá trị sau khi xáo trộn là không xác định.

Cùng một cách nếu chúng ta sử dụng HashPartitioner(2)

val rddTwoP = rdd.partitionBy(new HashPartitioner(2))

chúng ta sẽ nhận được 2 phân vùng:

rddTwoP.partitions.length
Int = 2

rddđược phân vùng theo dữ liệu chính sẽ không được phân phối thống nhất nữa:

countByPartition(rddTwoP).collect()
Array[Int] = Array(2, 4)

Bởi vì có ba phím và chỉ có hai giá trị khác nhau của hashCodemod nên numPartitionskhông có gì bất ngờ ở đây:

(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))

Chỉ để xác nhận những điều trên:

rddTwoP.mapPartitions(iter => Iterator(iter.map(_._1).toSet)).collect()
Array[scala.collection.immutable.Set[Int]] = Array(Set(2), Set(1, 3))

hash-partitioner-2

Cuối cùng, HashPartitioner(7)chúng ta nhận được bảy phân vùng, ba phân vùng không trống với 2 phần tử mỗi phân vùng:

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

Tóm tắt và ghi chú

  • HashPartitioner lấy một đối số xác định số lượng phân vùng
  • giá trị được gán cho các phân vùng bằng cách sử dụng hashcác khóa. hashchức năng có thể khác nhau tùy thuộc vào ngôn ngữ (Scala RDD có thể sử dụng hashCode, DataSetssử dụng MurmurHash 3, PySpark, portable_hash).

    Trong trường hợp đơn giản như thế này, khi khóa là một số nguyên nhỏ, bạn có thể giả định rằng đó hashlà một định danh ( i = hash(i)).

    API Scala sử dụng nonNegativeModđể xác định phân vùng dựa trên hàm băm được tính toán,

  • nếu sự phân bổ của các khóa không đồng nhất, bạn có thể gặp phải tình huống khi một phần của cụm của bạn không hoạt động

  • các phím phải có thể băm. Bạn có thể kiểm tra câu trả lời của tôi cho Danh sách A làm chìa khóa cho ReduceByKey của PySpark để đọc về các vấn đề cụ thể của PySpark. Một vấn đề có thể xảy ra khác được làm nổi bật bởi tài liệu HashPartitioner :

    Mảng Java có mã băm dựa trên danh tính của mảng chứ không phải nội dung của chúng, vì vậy việc cố gắng phân vùng RDD [Mảng [ ]] hoặc RDD [(Mảng [ ], _)] bằng HashPartitioner sẽ tạo ra kết quả không mong muốn hoặc không chính xác.

  • Trong Python 3, bạn phải đảm bảo rằng việc băm là nhất quán. Xem Ngoại lệ là gì: Tính ngẫu nhiên của hàm băm của chuỗi sẽ bị vô hiệu hóa thông qua PYTHONHASHSEED có nghĩa là trong pyspark?

  • Hash partitioner không phải là bị thương cũng không phải là khách quan. Nhiều khóa có thể được gán cho một phân vùng duy nhất và một số phân vùng có thể vẫn trống.

  • Xin lưu ý rằng các phương thức dựa trên băm hiện tại không hoạt động trong Scala khi kết hợp với các lớp trường hợp được xác định REPL ( Bình đẳng lớp trường hợp trong Apache Spark ).

  • HashPartitioner(hoặc bất kỳ hình thức nào khác Partitioner) xáo trộn dữ liệu. Trừ khi việc phân vùng được tái sử dụng giữa nhiều thao tác, nó không làm giảm lượng dữ liệu được xáo trộn.


Viết lên xuất sắc, cảm ơn bạn. Tuy nhiên, tôi nhận thấy trong các hình ảnh mà bạn có (1, None)với hash(2) % Pnơi P là phân vùng. Có nên không hash(1) % P?
javamonkey79,

Tôi đang sử dụng spark 2.2, và không có partitionByapi trong rdd. có một partitionBy trong dataframe.write, nhưng nó không lấy Partitioner làm đối số.
hakunami

câu trả lời tuyệt vời ... xáo trộn dựa trên người chia phần, một người chia sẻ tốt có thể giảm lượng dữ liệu bị xáo trộn.
Leon

Câu trả lời tuyệt vời. Tôi có một câu hỏi mà tôi không tìm được câu trả lời chắc chắn trên internet. Khi chúng ta sử dụng df.repartition (n), tức là khi chúng ta không chỉ định bất kỳ cột nào làm khóa, thì làm thế nào hàm băm hoạt động bên trong khi không có gì được băm?
dsk

@dsk, nếu bạn không chỉ định khóa, tôi tin rằng việc phân vùng lại sử dụng RoundRobinPartitioning. Một số thảo luận ở đây .
Mike Souder

6

RDDđược phân phối, điều này có nghĩa là nó được chia trên một số bộ phận. Mỗi phân vùng này có khả năng trên máy tính khác nhau. Trình phân vùng băm với đối số numPartitionschọn phân vùng nào để đặt cặp (key, value)theo cách sau:

  1. Tạo numPartitionsphân vùng chính xác .
  2. Các vị trí (key, value)trong phân vùng có sốHash(key) % numPartitions

3

Các HashPartitioner.getPartitionphương pháp có một chìa khóa như là đối số của nó và trả về chỉ số của phân vùng mà chiếc chìa khóa là để. Trình phân vùng phải biết các chỉ số hợp lệ là gì, vì vậy nó trả về các số trong phạm vi phù hợp. Số lượng phân vùng được chỉ định thông qua numPartitionsđối số của hàm tạo.

Việc triển khai trả về gần đúng key.hashCode() % numPartitions. Xem Partitioner.scala để biết thêm chi tiết.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.