该函数主要功能:通过指定的排序规则与进行排序操作的分区个数,对当前的RDD中的数据集按KEY进行排序,并生成一个SHUFFLEdrdd的实例,这个过程会运行shuffle操作,在运行排序操作前,sortBy操作会运行一次到两次的数据取样的操作,取出RDD中每一个PARTITION的部分数据,并依据进行分区的partition的个数,按key的compare大小把某个范围内的key放到一个指定的partition中进行排序.

该函数的操作演示样例:

import org.apache.spark.SparkContext._

*

*   val rdd: RDD[(String, Int)] = ...

*   implicit val caseInsensitiveOrdering = new Ordering[String] {

*     override def compare(a: String, b: String) =

a.toLowerCase.compare(b.toLowerCase)

*   }

*

*   // Sort by key, using the above case insensitive ordering.

*   rdd.sortByKey()

这上面的演示样例中定义implicit的隐试转换,

在OrderedRDDFunctions通过private val ordering = implicitly[Ordering[K]]引用

函数定义,由OrderedRDDFunctions类进行函数的实现:

这个函数中,传入两个參数,ascending表示是升序还是降序,默认true表示升序.

第二个參数是运行排序使用的partition的个数,默认是当前RDD的partition个数.

def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)

    : RDD[(K, V)] = self.withScope

{

生成运行分区操作的算子,这里生成的算子不再是默认的Hash算子,而是Range的算子,这个算子后面进行详细的分析.

  val part = new RangePartitioner(numPartitions, self, ascending)

这里又一次依据当前的RDD,生成一个新的RDD,这个shuffle的rdd中,不包括aggregator聚合函数.

也就是在shuffle的过程中仅仅是把key相应的值hash到相应的partition中,并依据key运行排序操作.

  new ShuffledRDD[K, V, V](self, part)

    .setKeyOrdering(if (ascending) ordering else ordering.reverse)

}

接下来看看RangePartitioner的排序算子的实现:

这里传入的參数包括三个,第一个是进行sort操作的分区数,第二个是当前的RDD,在生成新的RDD后,这个rdd就是ShuffledRDD的上层依赖,第三个表示升序或者降序.

class RangePartitioner[K : Ordering : ClassTag, V](

    partitions: Int,

    rdd: RDD[_ <: Product2[K, V]],

    private var ascending: Boolean = true)

  extends Partitioner {

在RangePartitioner实例生成时,会初始化一个rangeBounds的集合.这个的长度是partitions的长度减一.

private var rangeBounds: Array[K] = {

假设partitions仅仅有一个时,直接返回一个空的集合,由于这个rangeBounds的长度是partitions的值减一.

) {

    Array.empty

  } else {

这里得到一个大约的分区的样本量,最多不超过1e6(1000000)个,默认是分区个数的20倍.假设这个分区太多时,仅仅取1e6的个数.

    // This is the sample size we need to have roughly balanced output partitions,

capped at 1M.

    val sampleSize = math.min(20.0 * partitions, 1e6)

这里取出每一个partition中样本的最大的个数,通过当前的样本数量*3/partition的个数,并向上取整.

    // Assume the input partitions are roughly balanced and over-sample a little bit.

    val sampleSizePerPartition = math.ceil(3.0 * sampleSize /

rdd.partitions.size).toInt

这里会依据SHUFFLE前的RDD运行map->collect操作,得到每一个partition的样本信息,

每一个partition中最多取出ampleSizePerPartition的样本个数.

这里返回的sketched是一个数组,数组的长度是rdd的partitions的个数,

数组中每个元素是一个Iterator(partitionid,这个partition中总的数据条数,Array[key](长度是样本个数,或者一个小于样本个数的值(这样的情况表示partition的数据不够样本个数))),

这里读取每一个partition的样本时:

1,假设这个partition中的总的数据集小于ampleSizePerPartition个数时,取出这个partition的全部的数据,这个返回值中的样本个数也就是这个数据集的size.

2,这样的情况表示partition中的总数据集的个数大于(等于就不说了,直接返回)要取的样本数,一直对partition的数据进行迭代,并生成随机数(通过一个种子值与当前迭代到的条数进行乘法操作),得到的值假设是一个在样本个数范围内的值时,更新样本中相应位置的值.

    val (numItems, sketched) = RangePartitioner.sketch(rdd.map(_._1),

sampleSizePerPartition)

if (numItems == 0L) {

这样的情况表示没有数据,直接返回一个空集合.

      Array.empty

    } else {

这里使用到的numItems表示要进行排序操作的这个RDD中的总数据的条数.

通过取样的个数除上总的数据的条数,得到一个分数值.

      // If a partition contains much more than the average number of items, we re-sample

from it

      // to ensure that enough items are collected from that partition.

      val fraction = math.min(sampleSize / math.max(numItems, 1L), 1.0)

这个candidates中存储实用于计算排序的key的候选人信息,

      val candidates = ArrayBuffer.empty[(K, Float)]

这个集合中存储了部分partition中数据集的总数超过了平均每一个partition的数据集记录太多的数据的partition.

      val imbalancedPartitions = mutable.Set.empty[Int]

sketched.foreach { case (idx, n, sample) =>

这里迭代每个partition中的样本,假设partition中的数据量总数与样本在总记录中的占比进行乘法操作后的值大于每个partition要取的样本个数,把这个partition先记录下来.

否则依据这个partition的总记录数除上取样的数量得到一个权重,把这个partition中的样本加入到candidates的集合中.这个集合中的数据是排序的候选数据集.

        if (fraction * n > sampleSizePerPartition) {

          imbalancedPartitions += idx

        } else {

          // The weight is 1 over the sampling probability.

          val weight = (n.toDouble / sample.size).toFloat

          for (key <- sample) {

            candidates += ((key, weight))

          }

        }

      }

这里计算partition中的记录数比較多的partition,也就是记录数大于了平均每一个partition的数据集个数.须要对这些个partition进行又一次的取样,

      if (imbalancedPartitions.nonEmpty) {

这里依据须要又一次进行取样的partition生成一个PartitionPruningRDD实例.这个实例中仅仅计算须要进行又一次取样的partition.传入參数中的imbalancedPartitions.contains用于过滤partition

        // Re-sample imbalanced partitions with the desired sampling probability.

        val imbalanced = new PartitionPruningRDD(rdd.map(_._1),

imbalancedPartitions.contains)

)

这里运行一个取样操作,并通过collect得到取样的结果集.採用的是BernoulliSampler取样.

通过迭代每条数据依据传入的seed的种子值生成一个随机值,假设这个值小于传入的份数,把这个结果进行保留.详细的取样算法可见BernoulliSampler中的实现.

        val reSampled = imbalanced.sample(withReplacement = false, fraction,

seed).collect()

迭代得到的样本数据,加入到候选人的集合中.

        val weight = (1.0 / fraction).toFloat

        candidates ++= reSampled.map(x => (x, weight))

      }

这里依据候选人列表进行排序,返回一个数组,这个数组的长度是partitions的个数.

数组中每个下标位置存储一个key值,这个key就是区分这个分区数据的key的最大值.

这个过程主要是:

通过候选人列表中的每一个weight的的总和除上partitions的个数,得到每一个partition的一个平均的步长,開始对这个candidates(排序后的)进行迭代并对每条数据的weight进行相加操作,当这个值加到计算出来的这个步长时,同一时候当前迭代的key比上一个存储的key要大时,把这个key值存储起来.

      RangePartitioner.determineBounds(candidates, partitions)

    }

  }

}

RangePartitioner中处理的又一次分区函数:

在运行shuffle操作时,针对sortByKey操作的key的又一次分区操作的函数,

def getPartition(key: Any): Int = {

  val k = key.asInstanceOf[K]



) {

假设进行shuffle操作的又一次分区的分区个数小于128个时,直接从第0个分区開始迭代比較这个key应该在那个分区中,

    // If we have less than 128 partitions naive search

    while (partition < rangeBounds.length && ordering.gt(k, rangeBounds(partition)))

{



    }

  } else {

这样的情况表示分区个数比較多,通过二分查找的方式进行partition的查找.

    // Determine which binary search method to use only once.

    partition = binarySearch(rangeBounds, k)

    // binarySearch either returns the match location or -[insertion point]-1

) {



    }

    if (partition > rangeBounds.length) {

      partition = rangeBounds.length

    }

  }

这里得到分区后,依据正排还是倒排返回相应的分区.

  if (ascending) {

    partition

  } else {

    rangeBounds.length - partition

  }

}

spark transform系列__sortByKey的更多相关文章

  1. spark transform系列__groupByKey

    这个操作的作用依据同样的key的全部的value存储到一个集合中的一个玩意. def groupByKey(): RDD[(K, Iterable[V])] = self.withScope {  g ...

  2. Spark JDBC系列--取数的四种方式

    Spark JDBC系列--取数的四种方式 一.单分区模式 二.指定Long型column字段的分区模式 三.高自由度的分区模式 四.自定义option参数模式 五.JDBC To Other Dat ...

  3. rxjs5.X系列 —— transform系列 api 笔记

    欢迎指导与讨论:) 前言 本文是笔者翻译 RxJS 5.X 官网各类operation操作系列的的第一篇 -- transform转换.如有错漏,希望大家指出提醒O(∩_∩)O.更详细的资料尽在rxj ...

  4. spark学习系列

    转自: http://www.cnblogs.com/magj2006/p/4316264.html spark 系列文章汇总 源码导读 spark 源码导读1 从spark启动脚本开始 spark ...

  5. (转)Spark 算子系列文章

    http://lxw1234.com/archives/2015/07/363.htm Spark算子:RDD基本转换操作(1)–map.flagMap.distinct Spark算子:RDD创建操 ...

  6. spark transform操作卡死,请先对rdd进行action操作

    这两天一直在写spark程序,遇到了一个奇怪的问题. 问题简单描述如下,有两个RDD,设为rdd_a,rdd_b,当将这两个rdd合并的时候,spark会在运行中卡死. 解决方式也是奇葩. 只要在合并 ...

  7. Spark机器学习系列之13: 支持向量机SVM

    Spark 优缺点分析 以下翻译自Scikit. The advantages of support vector machines are: (1)Effective in high dimensi ...

  8. Spark 概念学习系列之从物理执行的角度透视spark Job(十七)

    本博文主要内容:  1.再次思考pipeline 2.窄依赖物理执行内幕 3.宽依赖物理执行内幕 4.Job提交流程 一:再次思考pipeline 即使采用pipeline的方式,函数f对依赖的RDD ...

  9. Spark 概念学习系列之从spark架构中透视job(十六)

    本博文的主要内容如下:  1.通过案例观察Spark架构 2.手动绘制Spark内部架构 3.Spark Job的逻辑视图解析 4.Spark Job的物理视图解析 1.通过案例观察Spark架构 s ...

随机推荐

  1. php递归取目录下的所有文件(原创)

    function get_dir_all_files($path) { $result=array(); $temp=array(); if(filetype($path)=='dir') { $di ...

  2. C#解除某类警告。。。。。。。。。。

    C#预处理器指令取消不必要的警告 今天将自己写的一个类库生成一个DLL后,想把注释也加进去.... 方法:在属性->生成选项卡->XML文档文件(勾选)(生成的文件名不能修改,使用时必须跟 ...

  3. 使用Micrisoft.net设计方案 第二章组织模式

    第二章组织模式 模式不仅依赖于它所包含的更小模式,同时也依赖包含它的更大的模式.它是描述复杂软件的系统方法. 本章的目标是让我们了解以下问题: 1.如何标识模式与模式的关系 2.如何把模式组织成模式集 ...

  4. css relative设置top为百分比值

    前言: 最近在学习HTML.CSS的过程中,想模仿一下百度首页.发现搜索框这一部分与上下其它元素的空白距离可以随着窗口大小变化(效果如下图所示),于是自己研究了一下并记录下来. 效果实现 <!D ...

  5. RawURL

    Request.RawUrl表示当前页面, Response.Redirect重新打开页面. 意思就是重新打开当前页面. 和下面一样的 string url=Request.RawUrl: Respo ...

  6. JAVA代理方式使用示例总结

    JAVA代理方式使用示例总结 一.    代理方式概括 Java的代理方式主要包含了静态代理,动态代理两种方式,其中,动态代理根据实现的方式不同,又可以划分为jdk动态代理和cglib动态代理. 二. ...

  7. Java 方法的应用

    Java方法可以理解为C#中的函数,都是把复杂的问题简单化,按模块,按功能区分,分别完成各个部分在调用这些方法完成整个功能. 方法的综合练习,猜数字的实现: 代码要求: 生成不重复的4位数字(只有1- ...

  8. LightOJ 1422 Halloween Costumes 【 区间dp 】

    区间dp的第一题----- 看题解看了好多~~终于看懂了---55555 dp[i][j] 表示第i天到第j天至少需要多少件衣服 那么第i件衣服只被第i天占用的话, dp[i][j] = dp[i+1 ...

  9. Mac 查看 剪贴板/剪切板/粘贴板 内容与格式

    命令行形式 osascript -e 'clipboard info' GUI 形式 Finder->编辑->显示剪贴板 图示:

  10. with as递归调用

    一.递归调用--在代码中偶尔看到以记之,便于下次学习 https://blog.csdn.net/johnf_nash/article/details/78681060 --查询节点及其下所有子节点 ...