spark transform系列__sortByKey
该函数主要功能:通过指定的排序规则与进行排序操作的分区个数,对当前的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的更多相关文章
- spark transform系列__groupByKey
这个操作的作用依据同样的key的全部的value存储到一个集合中的一个玩意. def groupByKey(): RDD[(K, Iterable[V])] = self.withScope { g ...
- Spark JDBC系列--取数的四种方式
Spark JDBC系列--取数的四种方式 一.单分区模式 二.指定Long型column字段的分区模式 三.高自由度的分区模式 四.自定义option参数模式 五.JDBC To Other Dat ...
- rxjs5.X系列 —— transform系列 api 笔记
欢迎指导与讨论:) 前言 本文是笔者翻译 RxJS 5.X 官网各类operation操作系列的的第一篇 -- transform转换.如有错漏,希望大家指出提醒O(∩_∩)O.更详细的资料尽在rxj ...
- spark学习系列
转自: http://www.cnblogs.com/magj2006/p/4316264.html spark 系列文章汇总 源码导读 spark 源码导读1 从spark启动脚本开始 spark ...
- (转)Spark 算子系列文章
http://lxw1234.com/archives/2015/07/363.htm Spark算子:RDD基本转换操作(1)–map.flagMap.distinct Spark算子:RDD创建操 ...
- spark transform操作卡死,请先对rdd进行action操作
这两天一直在写spark程序,遇到了一个奇怪的问题. 问题简单描述如下,有两个RDD,设为rdd_a,rdd_b,当将这两个rdd合并的时候,spark会在运行中卡死. 解决方式也是奇葩. 只要在合并 ...
- Spark机器学习系列之13: 支持向量机SVM
Spark 优缺点分析 以下翻译自Scikit. The advantages of support vector machines are: (1)Effective in high dimensi ...
- Spark 概念学习系列之从物理执行的角度透视spark Job(十七)
本博文主要内容: 1.再次思考pipeline 2.窄依赖物理执行内幕 3.宽依赖物理执行内幕 4.Job提交流程 一:再次思考pipeline 即使采用pipeline的方式,函数f对依赖的RDD ...
- Spark 概念学习系列之从spark架构中透视job(十六)
本博文的主要内容如下: 1.通过案例观察Spark架构 2.手动绘制Spark内部架构 3.Spark Job的逻辑视图解析 4.Spark Job的物理视图解析 1.通过案例观察Spark架构 s ...
随机推荐
- 谷歌浏览器(chrome) —— 扩展应用程序
工具的使用--谷歌浏览器(chrome) (二) 1. 设置和下载方法 右上角菜单按钮 ⇒ 更多工具(more tools) ⇒ 扩展(Extensions) 打开该页面之后,会首先进入扩展(已安装应 ...
- 2017-3-10 leetcode 229 238 268
今天登陆leetcode突然发现531被锁了,有种占了便宜的感觉哈哈哈! ================================================ leetcode229 Ma ...
- python 网络通讯 服务器端代码demo,能够同时处理多个客户端的连接请求
这是一个python网络通讯服务器端的代码demo,能够同时处理多个客户端的连接请求. from socket import * import threading from datetime impo ...
- POJ 1442 Treap模板
// by SiriusRen #include <cstdio> #include <cstring> #include <algorithm> using na ...
- JS实时获取浏览器窗口尺寸 .
给div实时设置宽度 <div id="detail" style="width: 100%; overflow: scroll;"> </d ...
- Aspose.Words将Word模板打印出来
利用Aspose.Words将制作好的模板,输出出来自己所需的文件 /// <summary> /// 打印信息和列表 /// </summary> /// <param ...
- 6) 十分钟学会android--Activity的生命周期之启动与销毁
当用户导航.退出和返回您的应用时,应用中的 Activity 实例将在其生命周期中转换不同状态. 例如,当您的Activity初次开始时,它将出现在系统前台并接收用户焦点. 在这个过程中,Androi ...
- WebStorm2018.2 破解 激活
1.进入http://idea.lanyus.com/,如图: 2.下载http://idea.lanyus.com/jar/JetbrainsCrack-3.1-release-enc.jar . ...
- 2nd
Java语言基础(常量的概述和使用) A:什么是常量 在程序执行的过程中其值不可以发生改变 B:Java中常量的分类 字面值常量 自定义常量(面向对象部分讲) C:字面值常量的分类 字符串常量 用双引 ...
- Asp.net Core 源码-PagedList<T>
using System.Collections.Generic; using System.Linq; using System; using System.Linq.Expressions; us ...