大数据开发-从cogroup的实现来看join是宽依赖还是窄依赖
前面一篇文章提到大数据开发-Spark Join原理详解,本文从源码角度来看cogroup 的join实现
1.分析下面的代码
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object JoinDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName(this.getClass.getCanonicalName.init).setMaster("local[*]")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
val random = scala.util.Random
val col1 = Range(1, 50).map(idx => (random.nextInt(10), s"user$idx"))
val col2 = Array((0, "BJ"), (1, "SH"), (2, "GZ"), (3, "SZ"), (4, "TJ"), (5, "CQ"), (6, "HZ"), (7, "NJ"), (8, "WH"), (0, "CD"))
val rdd1: RDD[(Int, String)] = sc.makeRDD(col1)
val rdd2: RDD[(Int, String)] = sc.makeRDD(col2)
val rdd3: RDD[(Int, (String, String))] = rdd1.join(rdd2)
println(rdd3.dependencies)
val rdd4: RDD[(Int, (String, String))] = rdd1.partitionBy(new HashPartitioner(3)).join(rdd2.partitionBy(new HashPartitioner(3)))
println(rdd4.dependencies)
sc.stop()
}
}
分析上面一段代码,打印结果是什么,这种join是宽依赖还是窄依赖,为什么是这样
2.从spark的ui界面来查看运行情况
关于stage划分和宽依赖窄依赖的关系,从2.1.3 如何区别宽依赖和窄依赖就知道stage与宽依赖对应,所以从rdd3和rdd4的stage的依赖图就可以区别宽依赖,可以看到join划分除了新的stage,所以rdd3的生成事宽依赖,另外rdd1.partitionBy(new HashPartitioner(3)).join(rdd2.partitionBy(new HashPartitioner(3)))
是另外的依赖图,所以可以看到partitionBy以后再没有划分新的 stage,所以是窄依赖。
3.join的源码实现
前面知道结论,是从ui图里面看到的,现在看join源码是如何实现的(基于spark2.4.5)
先进去入口方法,其中withScope的做法可以理解为装饰器,为了在sparkUI中能展示更多的信息。所以把所有创建的RDD的方法都包裹起来,同时用RDDOperationScope 记录 RDD 的操作历史和关联,就能达成目标。
/**
* 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`. Performs a hash join across the cluster.
*/
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))] = self.withScope {
join(other, defaultPartitioner(self, other))
}
下面来看defaultPartitioner
的实现,其目的就是在默认值和分区器之间取一个较大的,返回分区器
def defaultPartitioner(rdd: RDD[_], others: RDD[_]*): Partitioner = {
val rdds = (Seq(rdd) ++ others)
// 判断有没有设置分区器partitioner
val hasPartitioner = rdds.filter(_.partitioner.exists(_.numPartitions > 0))
//如果设置了partitioner,则取设置partitioner的最大分区数
val hasMaxPartitioner: Option[RDD[_]] = if (hasPartitioner.nonEmpty) {
Some(hasPartitioner.maxBy(_.partitions.length))
} else {
None
}
//判断是否设置了spark.default.parallelism,如果设置了则取spark.default.parallelism
val defaultNumPartitions = if (rdd.context.conf.contains("spark.default.parallelism")) {
rdd.context.defaultParallelism
} else {
rdds.map(_.partitions.length).max
}
// If the existing max partitioner is an eligible one, or its partitions number is larger
// than the default number of partitions, use the existing partitioner.
//主要判断传入rdd是否设置了默认的partitioner 以及设置的partitioner是否合法
//或者设置的partitioner分区数大于默认的分区数
//条件成立则取传入rdd最大的分区数,否则取默认的分区数
if (hasMaxPartitioner.nonEmpty && (isEligiblePartitioner(hasMaxPartitioner.get, rdds) ||
defaultNumPartitions < hasMaxPartitioner.get.getNumPartitions)) {
hasMaxPartitioner.get.partitioner.get
} else {
new HashPartitioner(defaultNumPartitions)
}
}
private def isEligiblePartitioner(
hasMaxPartitioner: RDD[_],
rdds: Seq[RDD[_]]): Boolean = {
val maxPartitions = rdds.map(_.partitions.length).max
log10(maxPartitions) - log10(hasMaxPartitioner.getNumPartitions) < 1
}
}
再进入join的重载方法,里面有个new CoGroupedRDD[K](Seq(self, other), partitioner)
def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))] = self.withScope {
this.cogroup(other, partitioner).flatMapValues( pair =>
for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, w)
)
}
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("HashPartitioner cannot partition array keys.")
}
//partitioner 通过对比得到的默认分区器,主要是分区器中的分区数
val cg = new CoGroupedRDD[K](Seq(self, other), partitioner)
cg.mapValues { case Array(vs, w1s) =>
(vs.asInstanceOf[Iterable[V]], w1s.asInstanceOf[Iterable[W]])
}
}
/**
* 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`. Performs a hash join across the cluster.
*/
def join[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (V, W))] = self.withScope {
join(other, new HashPartitioner(numPartitions))
}
最后来看CoGroupedRDD,这是决定是宽依赖还是窄依赖的地方,可以看到如果左边rdd的分区和上面选择给定的分区器一致,则认为是窄依赖,否则是宽依赖
override def getDependencies: Seq[Dependency[_]] = {
rdds.map { rdd: RDD[_] =>
if (rdd.partitioner == Some(part)) {
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)
}
}
}
总结,join时候可以指定分区数,如果join操作左右的rdd的分区方式和分区数一致则不会产生shuffle,否则就会shuffle,而是宽依赖,分区方式和分区数的体现就是分区器。
吴邪,小三爷,混迹于后台,大数据,人工智能领域的小菜鸟。
更多请关注
大数据开发-从cogroup的实现来看join是宽依赖还是窄依赖的更多相关文章
- 大数据开发实战:HDFS和MapReduce优缺点分析
一. HDFS和MapReduce优缺点 1.HDFS的优势 HDFS的英文全称是 Hadoop Distributed File System,即Hadoop分布式文件系统,它是Hadoop的核心子 ...
- 大数据开发-Spark-拷问灵魂的5个问题
1.Spark计算依赖内存,如果目前只有10g内存,但是需要将500G的文件排序并输出,需要如何操作? ①.把磁盘上的500G数据分割为100块(chunks),每份5GB.(注意,要留一些系统空间! ...
- 大数据开发-Flink-数据流DataStream和DataSet
Flink主要用来处理数据流,所以从抽象上来看就是对数据流的处理,正如前面大数据开发-Flink-体系结构 && 运行架构提到写Flink程序实际上就是在写DataSource.Tra ...
- 2019春招——Vivo大数据开发工程师面经
Vvio总共就一轮技术面+一轮HR面,技术面总体而言,比较宽泛,比较看中基础,面试的全程没有涉及简历上的东西(都准备好跟他扯项目了,感觉是抽取的题库...)具体内容如下: 1.熟悉Hadoop哪些组件 ...
- 杭州某知名xxxx公司急招大量java以及大数据开发工程师
因公司战略以及业务拓展,收大量java攻城狮以及大数据开发攻城狮. 职位信息: java攻城狮: https://job.cnblogs.com/offer/56032 大数据开发攻城狮: https ...
- 大数据开发实战:Stream SQL实时开发三
4.聚合操作 4.1.group by 操作 group by操作是实际业务场景(如实时报表.实时大屏等)中使用最为频繁的操作.通常实时聚合的主要源头数据流不会包含丰富的上下文信息,而是经常需要实时关 ...
- 大数据开发实战:Stream SQL实时开发二
1.介绍 本节主要利用Stream SQL进行实时开发实战,回顾Beam的API和Hadoop MapReduce的API,会发现Google将实际业务对数据的各种操作进行了抽象,多变的数据需求抽象为 ...
- 大数据开发实战:Stream SQL实时开发一
1.流计算SQL原理和架构 流计算SQL通常是一个类SQL的声明式语言,主要用于对流式数据(Streams)的持续性查询,目的是在常见流计算平台和框架(如Storm.Spark Streaming.F ...
- 大数据开发实战:Spark Streaming流计算开发
1.背景介绍 Storm以及离线数据平台的MapReduce和Hive构成了Hadoop生态对实时和离线数据处理的一套完整处理解决方案.除了此套解决方案之外,还有一种非常流行的而且完整的离线和 实时数 ...
随机推荐
- ctfhub技能树—RCE—过滤cat
打开靶机 查看页面信息 构造payload 127.0.0.1 || ls 题目提示过滤了cat,但我还是想试试 果然不行 网页访问没有结果,应该和上题一样被注释了,使用和同样的方法进行解题 利用命令 ...
- [Usaco2007 Dec]Building Roads 修建道路
题目描述 Farmer John最近得到了一些新的农场,他想新修一些道路使得他的所有农场可以经过原有的或是新修的道路互达(也就是说,从任一个农场都可以经过一些首尾相连道路到达剩下的所有农场).有些农场 ...
- STM32驱动LCD原理
TFTLCD即薄膜晶体管液晶显示器.它与无源TN-LCD.STN-LCD的简单矩阵不同,它在液晶显示屏的每一个像素上都设置有一个薄膜晶体管(TFT),可有效地克服非选通时的串扰,使显示液晶屏的静态特性 ...
- Junit测试和反射
Junit单元测试 测试分类 黑盒测试:不需要写代码,给输入值,看程序能否得到输出期望值. 白盒测试:需要些代码,关注程序具体的执行流程. Junit的使用 步骤 定义一个测试类(测试用例). 定义测 ...
- 20V,24V转5V,20V,24V转3.3V降压芯片,IC介绍
常用的20V和24V转5V,3.3V的LDO稳压和DC-DC降压芯片: PW6206系列是一款高精度,高输入电压,低静态电流,高速,低压降线性稳压器具有高纹波抑制.输入电压高达40V,负载电流高达10 ...
- 关于java并发场景下,HttpServletRequst中session丢失问题
使用场景: 在list数据进来之后使用安全数组 Lists.newCopyOnWriteArrayList() 进行了 parallelStream 并行处理,在接口中进行了登录者信息接口 ...
- 本地Mac通过堡垒机代理实现跨堡垒机scp问题
近日,公司在跳板机前架设了堡垒机,以防止ssh攻击,但这带来一个问题,我们平常直接ssh跳板机,可以直接使用scp来上传或下载跳板机数据到本地 架设堡垒之后经常使用的scp工具不好用了 于是本期就来解 ...
- 一文说通Dotnet的委托
简单的概念,也需要经常看看. 一.前言 先简单说说Delegate的由来.最早在C/C++中,有一个概念叫函数指针.其实就是一个内存指针,指向一个函数.调用函数时,只要调用函数指针就可以了,至于函 ...
- Jmeter-插件扩展及性能监控插件的安装
需要对http服务进行大数据量的传值测试:看看产品中的http服务,能支持传多少字符:目标值是希望能到10w+: 上次测试中,服务器总是内存满导致服务不响应,因此想增加对服务端的性能监控:查阅了smi ...
- JavaScript中创建数组的方式!
JavaScript中创建数组的方式! 利用数组字面量 // 1 直接量 console.log(Array.prototype); var arr = [1, 2, 4, 87432]; // 注意 ...