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. [原] KVM 虚拟化原理探究(1)— overview

    KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...

  2. BZOJ 1911: [Apio2010]特别行动队 [斜率优化DP]

    1911: [Apio2010]特别行动队 Time Limit: 4 Sec  Memory Limit: 64 MBSubmit: 4142  Solved: 1964[Submit][Statu ...

  3. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  4. Partition:增加分区

    在关系型 DB中,分区表经常使用DateKey(int 数据类型)作为Partition Column,每个月的数据填充到同一个Partition中,由于在Fore-End呈现的报表大多数是基于Mon ...

  5. .net 分布式架构之配置中心

    开源QQ群: .net 开源基础服务  238543768 开源地址: http://git.oschina.net/chejiangyi/Dyd.BaseService.ConfigManager ...

  6. Xamarin+Prism开发详解五:页面布局基础知识

    说实在的研究Xamarin到现在,自己就没设计出一款好的UI,基本都在研究后台逻辑之类的!作为Xamarin爱好者,一些简单的页面布局知识还是必备的. 布局常见标签: StackLayout Abso ...

  7. 第一个移动前端开源项目-dailog

    你还在为手机上没有忙碌光标而发愁吗?你还在抱怨弹出框组件要依赖zepto/jqery吗?你还在纠结是否要自己写一套还是去网上寻找成现成的UI组件吗?YouA为你轻松解决所有烦恼.YouA是我为移动前端 ...

  8. 【Big Data】HADOOP集群的配置(一)

    Hadoop集群的配置(一) 摘要: hadoop集群配置系列文档,是笔者在实验室真机环境实验后整理而得.以便随后工作所需,做以知识整理,另则与博客园朋友分享实验成果,因为笔者在学习初期,也遇到不少问 ...

  9. Entity Framework 延伸系列目录

    1.采用MiniProfiler监控EF与.NET MVC项目 2.采用EntityFramework.Extended 对EF进行扩展 3.EntityFramework执行存储过程中遇到的那些坑 ...

  10. Mach-O 的动态链接(Lazy Bind 机制)

    ➠更多技术干货请戳:听云博客 动态链接 要解决空间浪费和更新困难这两个问题最简单的方法就是把程序的模块相互分割开来,形成独立的文件,而不再将它们静态的链接在一起.简单地讲,就是不对那些组成程序的目标文 ...