spark 排序实现原理 RangePartitioner

sprak Core中比较常用的排序方法sortBy和sortKByKey,这是一个shuffle类算法子,宽依赖,出发DAGSchedular划分Stage,那么他们排序的原理是啥呢?

第一步Stage0:
分区采样Sample,创建RangePartitioner,先对输入的数据的key做采样,来估算Key的分布,然后按照指定的排序切分range,尽量让每个partition对应的range里的key分布均匀,计算出一个RangeBounds,长度为partitions - 1的Array[Key],里面存放的数据为前partition - 1分区中每个分区的key的上界,最后一个partition就是存放剩余的其他数据
此时的采样大小:

//此处确定采样大小,前提假设数据分区大致平衡,即不存在倾斜的情况
// 总体采样数量
val sampleSize = math.min(20.0 * partitions, 1e6)
// 每个分区采样数量
val sampleSizePerPartition = math.ceil(3.0 * sampleSize / rdd.partitions.length).toInt
// 数据采样
val (numItems, sketched) = RangePartitioner.sketch(rdd.map(_._1), sampleSizePerPartition)
// 此方法数据采样 返回的数据 
def sketch[K : ClassTag](
   rdd: RDD[K],
   sampleSizePerPartition: Int): (Long, Array[(Int, Long, Array[K])]) = {
 val shift = rdd.id
 // val classTagK = classTag[K] // to avoid serializing the entire partitioner object
 val sketched = rdd.mapPartitionsWithIndex { (idx, iter) =>
   val seed = byteswap32(idx ^ (shift << 16))
   val (sample, n) = SamplingUtils.reservoirSampleAndCount(
     iter, sampleSizePerPartition, seed)
   Iterator((idx, n, sample))
 }.collect()
 val numItems = sketched.map(_._2).sum
 (numItems, sketched)
}
如果一个分区包含的项远远超过了平均数,我们将从中重新采样以确保从该分区收集足够的项。
val sampleSizePerPartition = math.ceil(3.0 * sampleSize / rdd.partitions.length).toInt
      val (numItems, sketched) = RangePartitioner.sketch(rdd.map(_._1), sampleSizePerPartition)
      if (numItems == 0L) {
        Array.empty
      } else {
        // 采样的数据占整体的比例
        val fraction = math.min(sampleSize / math.max(numItems, 1L), 1.0)
        val candidates = ArrayBuffer.empty[(K, Float)]
        val imbalancedPartitions = mutable.Set.empty[Int]
        sketched.foreach { case (idx, n, sample) =>
          if (fraction * n > sampleSizePerPartition) {
            imbalancedPartitions += idx
          } else {
            val weight = (n.toDouble / sample.length).toFloat
            for (key <- sample) {
              candidates += ((key, weight))
            }
          }
        }
        if (imbalancedPartitions.nonEmpty) {
          // 以期望的采样概率重新采样不平衡的分区
          val imbalanced = new PartitionPruningRDD(rdd.map(_._1), imbalancedPartitions.contains)
          val seed = byteswap32(-rdd.id - 1)
          val reSampled = imbalanced.sample(withReplacement = false, fraction, seed).collect()
          val weight = (1.0 / fraction).toFloat
          candidates ++= reSampled.map(x => (x, weight))
        }
        RangePartitioner.determineBounds(candidates, partitions)
      }

第二步:shuffle write
开始shuffle在map side做shuffle write,每个计算节点根据前面计算出来的rangeBounds对输入数据重新分片,分片采用rangePartitioner 使得重新分区后的数据在分区之间只排好序的,
然后此时分区内的数据不一定是已经排好序

第三步:shuffle read
在reducer side,对每个分区内的数据做排序
这样完成之后,partition之间的数据在map side就保证有序,而每个分区内的数据在reducer side也保证有排序,从而达到了全局排序的效果
大致流程图如下:
在这里插入图片描述

此时引发一个问题:我相信很多小伙伴在面试的时候都遇到过这个问题:给定1T的文件和只有1G内存的机器,如何实现全排序?
1,产生随机数,生成10个测试文件,10个线程同时进行。
2,将大文件分割1BM的小文件,每个线程对分割而成的内容进行内部排序后,写入文件,利用自定义阻塞线程池,每次同时写入3~4个文件。
3,将所有小文件排序后,利用多路排序算法将小文件写入最终文件。

如果可以的话是否也可采用类似spark排序的的原理呢?欢迎交流!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值