Scala 如何为具有相同大小分区的Spark RDD定义自定义分区器,其中每个分区具有相同数量的元素?

Scala 如何为具有相同大小分区的Spark RDD定义自定义分区器,其中每个分区具有相同数量的元素?,scala,hadoop,apache-spark,Scala,Hadoop,Apache Spark,我是新手。我有一个很大的元素数据集[RDD],我想把它分成两个大小完全相同的分区,以保持元素的顺序。我试着像这样使用RangePartitioner var data = partitionedFile.partitionBy(new RangePartitioner(2, partitionedFile)) 这并不能给出令人满意的结果,因为它粗略地划分了元素的大小,但并不完全相等。 例如,如果有64个元素,我们使用 Rangepartitioner,然后将其分为31个元素和33个元素 我需要

我是新手。我有一个很大的元素数据集[RDD],我想把它分成两个大小完全相同的分区,以保持元素的顺序。我试着像这样使用
RangePartitioner

var data = partitionedFile.partitionBy(new RangePartitioner(2, partitionedFile))
这并不能给出令人满意的结果,因为它粗略地划分了元素的大小,但并不完全相等。 例如,如果有64个元素,我们使用
Rangepartitioner
,然后将其分为31个元素和33个元素

我需要一个分区器,这样我就可以在一半中得到前32个元素,而另一半包含第二组32个元素。
你能帮我建议一下如何使用定制的分区器,这样我就可以得到大小相等的两半,保持元素的顺序吗?

分区器通过为分区分配一个键来完成工作。您需要事先了解密钥分发,或者查看所有密钥,才能创建这样的分区器。这就是为什么Spark不为您提供一个

一般来说,您不需要这样的分区器。事实上,我无法想出一个需要相同大小分区的用例。如果元素的数量是奇数呢

不管怎样,假设您有一个由sequential
Int
s键控的RDD,您知道总共有多少个。然后您可以编写一个自定义的
分区器
,如下所示:

class ExactPartitioner[V](
    partitions: Int,
    elements: Int)
  extends Partitioner {

  def getPartition(key: Any): Int = {
    val k = key.asInstanceOf[Int]
    // `k` is assumed to go continuously from 0 to elements-1.
    return k * partitions / elements
  }
}

这个答案从Daniel那里得到了一些启发,但提供了一个完整的实现(使用pimp my library模式),并为人们的复制和粘贴需求提供了一个示例:)


免责声明:未经测试,只是将此直接写入SO答案中,在更新版本的Spark中,您可以自己编写并使用此方法

我们的想法是

  • 索引您的RDD
  • 使用索引作为键
  • 根据所需分区的数量应用自定义分区器
示例代码如下所示:

//定义自定义分区器类
类EqualDistributionPartitioner(numberOfPartitions:Int)扩展了分区器{
覆盖def numPartitions:Int=numberOfPartitions
覆盖def getPartition(键:Any):Int={
(键A安装[Long]%的分区数)。toInt
}
}
//创建测试RDD(从一个分区开始)
val testDataRaw=Seq(
(“第1区”、“第2区”),
(“第1区”、“第2区”),
(“场1_c”、“场2_c”),
(“第1区”、“第2区”),
(“第1区”、“第2区”),
(“第1区”、“第2区”),
(“第1场”、“第2场”),
(“第1区”、“第2区”),
(“field1_k”、“field2_k”),
(“第1区”、“第2区”),
(“现场1μm”、“现场2μm”),
(“字段1\n”、“字段2\n”)
)
val testRdd:RDD[(字符串,字符串)]=spark.sparkContext.parallelize(testDataRaw,1)
//创建索引
val testRddWithIndex:RDD[(长,(字符串,字符串))]=testRdd.zipWithIndex().map(msg=>(msg.\u 2,msg.\u 1))
//使用索引进行平均分配
//具有六个分区的示例
println(“具有2个分区的示例:”)
val equallyDistributedPartitionTwo=testRddWithIndex.partitionBy(新的EqualDistributionPartitioner(2))
equallyDistributedPartitionTwo.foreach(k=>println(s“Partition:${TaskContext.getPartitionId()},内容:$k”))
println(“\n具有4个分区的示例:”)
//具有四个分区的示例
val equallyDistributedPartitionFour=testRddWithIndex.partitionBy(新的EqualDistributionPartitioner(4))
equallyDistributedPartitionFour.foreach(k=>println(s“Partition:${TaskContext.getPartitionId()},内容:$k”))
其中
spark
是您的
SparkSession

作为输出,您将获得:

带有2个分区的示例:
分区:0,内容:(0,(字段1\u a,字段2\u a))
分区:1,内容:(1,(字段1\u b,字段2\u b))
分区:0,内容:(2,(field1_c,field2_c))
分区:1,内容:(3,(字段1,字段2)
分区:0,内容:(4,(字段1,字段2)
分区:1,内容:(5,(字段1,字段2)
分区:0,内容:(6,(字段1,字段2)
分区:1,内容:(7,(字段1,字段2)
分区:0,内容:(8,(field1\u k,field2\u k))
分区:1,内容:(9,(字段1,字段2)
分区:0,内容:(10,(字段1,字段2)
分区:1,内容:(11,(字段1,字段2)
具有4个分区的示例:
分区:0,内容:(0,(字段1\u a,字段2\u a))
分区:0,内容:(4,(字段1,字段2)
分区:0,内容:(8,(field1\u k,field2\u k))
分区:3,内容:(3,(字段1,字段2)
分区:3,内容:(7,(字段1,字段2)
分区:3,内容:(11,(字段1,字段2)
分区:1,内容:(1,(字段1\u b,字段2\u b))
分区:1,内容:(5,(字段1,字段2)
分区:1,内容:(9,(字段1,字段2)
分区:2,内容:(2,(字段1_c,字段2_c))
分区:2,内容:(6,(字段1,字段2)
分区:2,内容:(10,(字段1,字段2)

感谢Daniel的回复。它成功了。我正在研究一种算法,该算法在数据集中包含偶数个元素。一旦您定义了这个新类,您将在哪里调用它?RDD中的分区器是val,我无法更改它,如果我用这个自定义分区器定义了一个新的RDD,我如何用方法创建它?Skew会引入额外的处理时间。它是通过让一个执行者比另一个执行者有更多的挂起任务,或者因为分区的大小不同(一个任务比另一个任务运行的时间长)。我通常会说,过度调度更多(比可用的内核多得多)和更小的分区,以使偏差消失在噪音中。比在单个执行器上精确匹配任务要好。请注意,还有另一种方法可以影响分区方式。默认情况下,它使用的是,因此通过重写您的方法,您还可以直接影响分区,但解决方案确实有效。但是,在这种情况下,当密钥为'4',分区数为'2',元素数为'4'时,如何将密钥分配给分区?具有给定参数的Bcoz,即“Pa”
import RDDConversions._

trait RDDWrapper[T] {
  def rdd: RDD[T]
}

// TODO View bounds are deprecated, should use context bounds
// Might need to change ClassManifest for ClassTag in spark 1.0.0
case class RichPairRDD[K <% Ordered[K] : ClassManifest, V: ClassManifest](
  rdd: RDD[(K, V)]) extends RDDWrapper[(K, V)] {
  // Here we use a single Long to try to ensure the sort is balanced, 
  // but for really large dataset, we may want to consider
  // using a tuple of many Longs or even a GUID
  def sortByKeyGrouped(numPartitions: Int): RDD[(K, V)] =
    rdd.map(kv => ((kv._1, Random.nextLong()), kv._2)).sortByKey()
    .grouped(numPartitions).map(t => (t._1._1, t._2))
}

case class RichRDD[T: ClassManifest](rdd: RDD[T]) extends RDDWrapper[T] {
  def grouped(size: Int): RDD[T] = {
    // TODO Version where withIndex is cached
    val withIndex = rdd.mapPartitions(_.zipWithIndex)

    val startValues =
      withIndex.mapPartitionsWithIndex((i, iter) => 
        Iterator((i, iter.toIterable.last))).toArray().toList
      .sortBy(_._1).map(_._2._2.toLong).scan(-1L)(_ + _).map(_ + 1L)

    withIndex.mapPartitionsWithIndex((i, iter) => iter.map {
      case (value, index) => (startValues(i) + index.toLong, value)
    })
    .partitionBy(new Partitioner {
      def numPartitions: Int = size
      def getPartition(key: Any): Int = 
        (key.asInstanceOf[Long] * numPartitions.toLong / startValues.last).toInt
    })
    .map(_._2)
  }
}
// TODO modify above to be implicit class, rather than have implicit conversions
object RDDConversions {
  implicit def toRichRDD[T: ClassManifest](rdd: RDD[T]): RichRDD[T] = 
    new RichRDD[T](rdd)
  implicit def toRichPairRDD[K <% Ordered[K] : ClassManifest, V: ClassManifest](
    rdd: RDD[(K, V)]): RichPairRDD[K, V] = RichPairRDD(rdd)
  implicit def toRDD[T](rdd: RDDWrapper[T]): RDD[T] = rdd.rdd
}
import RDDConversions._

yourRdd.grouped(2)