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. 通过Jexus 部署 dotnetcore版本MusicStore 示例程序

    ASPNET Music Store application 是一个展示最新的.NET 平台(包括.NET Core/Mono等)上使用MVC 和Entity Framework的示例程序,本文将展示 ...

  2. Hyper-V无法文件拖拽解决方案~~~这次用一个取巧的方法架设一个FTP来访问某个磁盘,并方便的读写文件

    异常处理汇总-服 务 器 http://www.cnblogs.com/dunitian/p/4522983.html 服务器相关的知识点:http://www.cnblogs.com/dunitia ...

  3. 关于Layer弹出框初探

    layer至今仍作为layui的代表作,她的受众广泛并非偶然,而是这五年多的坚持,不断完善和维护.不断建设和提升社区服务,使得猿们纷纷自发传播,乃至于成为今天的Layui最强劲的源动力.目前,laye ...

  4. 微信网页开发之获取用户unionID的两种方法--基于微信的多点登录用户识别

    假设网站A有以下功能需求:1,pc端微信扫码登录:2,微信浏览器中的静默登录功能需求,这两种需求就需要用到用户的unionID,这样才能在多个登录点(终端)识别用户.那么这两种需求下用户的unionI ...

  5. 如何理解DT将是未来IT的转型之路?

    如今的IT面临着内忧外患的挑战. 一方面,企业多多少少都建立了信息化,有些企业或集团甚至会有数几十个分公司,包含直销.代理.零售以及第三方物流等多种业态.越是复杂的业务,信息化建设越困难,比如运用大量 ...

  6. 关于Genymotion下载比较慢的解决办法

    Genymotion号称Android模拟器中运行最快的,但是服务器在国外,Android镜像下载起来那个速度就不想说了. Add new device后下载速度太慢了,容易失败 先登录,然后add, ...

  7. wireshark 相关提示

    Packet size limited during capture      提示说明标记的包没有抓全,在某些操作系统中,默认只抓96个字节,tcpdump中有"-s"参数可用于 ...

  8. 第六代智能英特尔® 酷睿™ 处理器图形 API 开发人员指南

    欢迎查看第六代智能英特尔® 酷睿™ 处理器图形 API 开发人员指南,该处理器可为开发人员和最终用户提供领先的 CPU 和图形性能增强.各种新特性和功能以及显著提高的性能. 本指南旨在帮助软件开发人员 ...

  9. AOP之Castle DynamicProxy 动态代理

    这里主要介绍使用castle这个动态代理,在.net一些开源的框架里可以找到它的影子,就连微软的rchard也是使用这个进行方法拦截等可以基于这个进行方法拦截,在这个方面PostSharp算是比较好用 ...

  10. ZooKeeper简介

    本文中,我们将对ZooKeeper进行介绍.简单地说,ZooKeeper是一个用来在构成应用的各个子服务之间进行协调的一个服务. 由于其本身并没有特别复杂的机制,因此我们将会把更多的笔墨集中在如何对Z ...