PairRDDFunctions类提供了以下两个join接口,只提供一个参数,不指定分区函数时默认使用HashPartitioner;提供numPartitions参数时,其内部的分区函数是HashPartitioner(numPartitions)
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))] = self.withScope {
//这里的defaultPartitioner 就是HashPartitioner,如果指定了HashPartitioner
//分区数由spark.default.parallism数指定,如果未指定就取分区数大的
join(other, defaultPartitioner(self, other))
}
def join[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (V, W))] = self.withScope {
//指定分区数目
join(other, new HashPartitioner(numPartitions))
}

以上两个join接口都是调用的这个方法:  

rdd.join的实现:rdd1.join(rdd2) => rdd1.cogroup(rdd2,partitioner)

/**
* Return an RDD containing all pairs of elements with matching keys in `this` and `other`. Each
* pair of elements will be returned as a (k, (v1, v2)) tuple, where (k, v1) is in `this` and
* (k, v2) is in `other`. Uses the given Partitioner to partition the output RDD.
*/
def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))] = self.withScope {
//rdd.join的实现:rdd1.join(rdd2) => rdd1.cogroup(rdd2,partitioner) => flatMapValues(遍历两个value的迭代器)
   //最后返回的是(key,(v1,v2))这种形式的元组
this.cogroup(other, partitioner).flatMapValues( pair => for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, w) ) }
跟到cogroup方法
  /**
* For each key k in `this` or `other`, return a resulting RDD that contains a tuple with the
* list of values for that key in `this` as well as `other`.
*/
def cogroup[W](other: RDD[(K, W)], partitioner: Partitioner)
: RDD[(K, (Iterable[V], Iterable[W]))] = self.withScope {
if (partitioner.isInstanceOf[HashPartitioner] && keyClass.isArray) {
throw new SparkException("Default partitioner cannot partition array keys.")
}
/**
* 这里构造一个CoGroupedRDD,也就是 cg = new CoGroupedRDD(Seq(rdd1,rdd2),partitioner)
* 其键值对中的value要求是Iterable[V]和Iterable[W]类型
* 下面了解CoGroupedRDD这个类,看是怎么构造的
*/
val cg = new CoGroupedRDD[K](Seq(self, other), partitioner)
cg.mapValues { case Array(vs, w1s) =>
(vs.asInstanceOf[Iterable[V]], w1s.asInstanceOf[Iterable[W]])
}
}

这是CoGroupedRDD的类声明,其中有两个与java 语法的不同:

1.类型声明中的小于号“<”,这个在scala 中叫做变量类型的上界,也就是原类型应该是右边类型的子类型,具体参见《快学scala》的17.3节

2.@transient:这个是瞬时变量注解,不用进行序列化 ,也可以参见《快学Scala》的15.3节

/*
* 这里返回的rdd的类型是(K,Array[Iterable[_]]),即key不变,value为所有对应这个key的value的迭代器的数组
*/
class CoGroupedRDD[K: ClassTag](
@transient var rdds: Seq[RDD[_ <: Product2[K, _]]],
part: Partitioner)
extends RDD[(K, Array[Iterable[_]])](rdds.head.context, Nil)

看看这个RDD的依赖以及如何分区的

再看这两个函数之前,最好先了解下这两个类是干什么的:

1.CoGroupPartition是Partition的一个子类,其narrowDeps是NarrowCoGroupSplitDep类型的一个数组

/**
* 这里说到CoGroupPartition 包含着父RDD依赖的映射关系,
* @param index:可以看到CoGroupPartition 将index作为哈希code进行分区
* @param narrowDeps:narrowDeps是窄依赖对应的分区数组
*/
private[spark] class CoGroupPartition(
override val index: Int, val narrowDeps: Array[Option[NarrowCoGroupSplitDep]])
extends Partition with Serializable {
override def hashCode(): Int = index
override def equals(other: Any): Boolean = super.equals(other)
}

2.这个NarrowCoGroupSplitDep的主要功能就是序列化,为了避免重复,对rdd做了瞬态注解

/*
* 这个NarrowCoGroupSplitDep的主要功能就是序列化,为了避免重复,对rdd做了瞬态注解
*/
private[spark] case class NarrowCoGroupSplitDep(
@transient rdd: RDD[_], //瞬态的字段不会被序列化,适用于临时变量
@transient splitIndex: Int,
var split: Partition
) extends Serializable { @throws(classOf[IOException])
private def writeObject(oos: ObjectOutputStream): Unit = Utils.tryOrIOException {
// Update the reference to parent split at the time of task serialization
split = rdd.partitions(splitIndex)
oos.defaultWriteObject()
}
}

回到CoGroupedRDD上来,先看这个RDD的依赖是如何划分的:

  /*
* 简单看下CoGroupedRDD重写的RDD的getDependencies:
* 如果rdd和给定分区函数相同就是窄依赖
* 否则就是宽依赖
*/
override def getDependencies: Seq[Dependency[_]] = {
rdds.map { rdd: RDD[_] =>
if (rdd.partitioner == Some(part)) {
/*如果两个RDD的分区函数和join时指定的分区函数相同,则对应窄依赖*/
logDebug("Adding one-to-one dependency with " + rdd)
new OneToOneDependency(rdd)
} else {
logDebug("Adding shuffle dependency with " + rdd)
new ShuffleDependency[K, Any, CoGroupCombiner](
rdd.asInstanceOf[RDD[_ <: Product2[K, _]]], part, serializer)
}
}
}

CoGroupedRDD.getPartitions 返回一个带有Partitioner.numPartitions个分区类型为CoGroupPartition的数组

  /*
* 这里返回一个带有Partitioner.numPartitions个分区类型为CoGroupPartition的数组
*/
override def getPartitions: Array[Partition] = {
val array = new Array[Partition](part.numPartitions)
for (i <- 0 until array.length) {
// Each CoGroupPartition will have a dependency per contributing RDD //rdds.zipWithIndex 这个是生成一个(rdd,rddIndex)的键值对,可以查看Seq或者Array的API
//继续跟到CoGroupPartition这个Partition,其是和Partition其实区别不到,只是多了一个变量narrowDeps
//回来看NarrowCoGroupSplitDep的构造,就是传入了每一个rdd和分区索引,以及分区,其可以将分区序列化
array(i) = new CoGroupPartition(i, rdds.zipWithIndex.map { case (rdd, j) =>
// Assume each RDD contributed a single dependency, and get it
dependencies(j) match {
case s: ShuffleDependency[_, _, _] => None
case _ => Some(new NarrowCoGroupSplitDep(rdd, i, rdd.partitions(i)))
}
}.toArray)
}
array
}

好,现在弱弱的总结下CoGroupedRDD,其类型大概是(k,(Array(CompactBuffer[v1]),Array(CompactBuffer[v2]))),这其中用到了内部的封装,以及compute函数的实现

有兴趣的可以继续阅读下源码,这一部分就不介绍了。

下面还是干点正事,把join算子的整体简单理一遍:

1.join 算子内部使用了cogroup算子,这个算子返回的是(key,(v1,v2))这种形式的元组

2.深入cogroup算子,发现其根据rdd1,rdd2创建了一个CoGroupedRDD

3.简要的分析了CoGroupedRDD的依赖关系,看到如果两个rdd的分区函数相同,那么生成的rdd分区数不变,它们之间是一对一依赖,也就是窄依赖,从而可以减少依次shuffle

4. CoGroupedRDD的分区函数就是将两个rdd的相同分区索引的分区合成一个新的分区,并且通过NarrowCoGroupSplitDep这个类实现了序列化

5.具体的合并过程还未记录,之后希望可以补上这部分的内容

这里简单做了一个实验:https://github.com/Tongzhenguo/my_scala_code/blob/master/src/main/scala/person/tzg/scala/test/MyJoinTest.scala

  

Spark join 源码跟读记录的更多相关文章

  1. Spark 源码浅读-SparkSubmit

    Spark 源码浅读-任务提交SparkSubmit main方法 main方法主要用于初始化日志,然后接着调用doSubmit方法. override def main(args: Array[St ...

  2. (升级版)Spark从入门到精通(Scala编程、案例实战、高级特性、Spark内核源码剖析、Hadoop高端)

    本课程主要讲解目前大数据领域最热门.最火爆.最有前景的技术——Spark.在本课程中,会从浅入深,基于大量案例实战,深度剖析和讲解Spark,并且会包含完全从企业真实复杂业务需求中抽取出的案例实战.课 ...

  3. 第九篇:Spark SQL 源码分析之 In-Memory Columnar Storage源码分析之 cache table

    /** Spark SQL源码分析系列文章*/ Spark SQL 可以将数据缓存到内存中,我们可以见到的通过调用cache table tableName即可将一张表缓存到内存中,来极大的提高查询效 ...

  4. Spark SQL源码解析(二)Antlr4解析Sql并生成树

    Spark SQL原理解析前言: Spark SQL源码剖析(一)SQL解析框架Catalyst流程概述 这一次要开始真正介绍Spark解析SQL的流程,首先是从Sql Parse阶段开始,简单点说, ...

  5. 第七篇:Spark SQL 源码分析之Physical Plan 到 RDD的具体实现

    /** Spark SQL源码分析系列文章*/ 接上一篇文章Spark SQL Catalyst源码分析之Physical Plan,本文将介绍Physical Plan的toRDD的具体实现细节: ...

  6. Spark SQL源码解析(四)Optimization和Physical Planning阶段解析

    Spark SQL原理解析前言: Spark SQL源码剖析(一)SQL解析框架Catalyst流程概述 Spark SQL源码解析(二)Antlr4解析Sql并生成树 Spark SQL源码解析(三 ...

  7. Spark Streaming源码解读之JobScheduler内幕实现和深度思考

    本期内容 : JobScheduler内幕实现 JobScheduler深度思考 JobScheduler 是整个Spark Streaming调度的核心,需要设置多线程,一条用于接收数据不断的循环, ...

  8. spark最新源码下载并导入到开发环境下助推高质量代码(Scala IDEA for Eclipse和IntelliJ IDEA皆适用)(以spark2.2.0源码包为例)(图文详解)

    不多说,直接上干货! 前言   其实啊,无论你是初学者还是具备了有一定spark编程经验,都需要对spark源码足够重视起来. 本人,肺腑之己见,想要成为大数据的大牛和顶尖专家,多结合源码和操练编程. ...

  9. 使用 IntelliJ IDEA 导入 Spark 最新源码及编译 Spark 源代码(博主强烈推荐)

    前言   其实啊,无论你是初学者还是具备了有一定spark编程经验,都需要对spark源码足够重视起来. 本人,肺腑之己见,想要成为大数据的大牛和顶尖专家,多结合源码和操练编程. 准备工作 1.sca ...

随机推荐

  1. 调用AJAX做登陆和注册

    先建立一个页面来检测一下我们建立的用户名能不能用,看一下有没有已经存在的用户名吗 可以通过ajax提示一下 $("#uid").blur(function(){ //取用户名 va ...

  2. 简单有效的kmp算法

    以前看过kmp算法,当时接触后总感觉好深奥啊,抱着数据结构的数啃了一中午,最终才大致看懂,后来提起kmp也只剩下“奥,它是做模式匹配的”这点干货.最近有空,翻出来算法导论看看,原来就是这么简单(先不说 ...

  3. InstallShield 脚本语言学习笔记

    InstallShield脚本语言是类似C语言,利用InstallShield的向导或模板都可以生成基本的脚本程序框架,可以在此基础上按自己的意愿进行修改和添加.     一.基本语法规则      ...

  4. Shell碎碎念

    1. 字符串如何大小写转换 str="This is a Bash Shell script." 1> tr方式 newstr=`tr '[A-Z]' '[a-z]' < ...

  5. Velocity初探小结--velocity使用语法详解

    做java开发的朋友一般对JSP是比较熟悉的,大部分人第一次学习开发View层都是使用JSP来进行页面渲染的,我们都知道JSP是可以嵌入java代码的,在远古时代,java程序员甚至在一个jsp页面上 ...

  6. 在centos7上安装ClamAV杀毒,并杀毒(centos随机英文10字母)成功

    前言 上传文件的时候发现总是失败,查看top发现有个进程一直cpu占用80%以上,而且名称还是随机数.kill之后,一会儿又重新生成了.突然发现居然没有在服务端杀毒的经历.在此处补齐. 安装clama ...

  7. php cryptr 加密函数

    class CryptHelper { /** * 加密 * @param unknown $password * @param unknown $salt * @return string */ p ...

  8. Android SDK 与API版本对应关系

    Android SDK版本号 与 API Level 对应关系如下表: Code name Version API level   (no code name) 1.0 API level 1   ( ...

  9. Xcode 锁终端

    锁终端 输入: <1>cd /Applications/Xcode.app 回车 结果显示: Xcode.app 输入: <2>sudo chown -hR root:whee ...

  10. 搭建个人wordpress博客(小白教程)

    新浪sae平台现在是有个免费个人空间使用,现在,教您如何使用该平台搭建属于自己的个人网站,本教程以wordpress程序安装包搭建个人网站. 申请新浪云账号 如果我们使用SAE新浪云计算平台作为服务器 ...