什么是宽窄依赖,及特殊join算子,join时何时产生shuffle,何时不产生shuffle

本文深入探讨了Spark中宽依赖和窄依赖的概念,详细解释了它们的产生原因及影响因素,尤其是在shuffle操作中的表现。通过具体示例,阐述了join操作在不同场景下是否触发shuffle的条件,以及groupByKey对join操作的影响。

1、 什么是宽窄依赖,
宽依赖: 发生shuffle时,一定会产生宽依赖,宽依赖是一个RDD中的一个Partition被多个子Partition所依赖(一个父亲多有儿子),也就是说每一个父RDD的Partition中的数据,都可能传输一部分到下一个RDD的多个partition中,此时一定会发生shuffle

窄依赖: 一个RDD中的一个 Partition最多 被一个 子 Partition所依赖(一个父亲有一个儿子)

2、 Spark中产生宽窄依赖的依据是shuffle,当发生shuffle时,会产生宽依赖,基本上shuffle算子都会产生宽依赖,但是join除外,在执行join算子之前如果先执行groupByKey,执行groupByKey之后,会把相同的key分到同一个分区,再执行join算子,join算子是把key相同的进行join(只是对于k v形式的数据可以使用),不一定会产生shuffle ,有可能发生shuffle,也有可能不发生

最后返回的结果是(Key,(rdd1的v,rdd2的v)),如下平行化创建,两个RDD,对其进行Join。这中情况下就不一定会产生shuffle,根据具体情况而言

#第一种情况, 不使用groupByKey直接进行join
 val rdd1 = sc.parallelize(List("aa"->5,"bb"->5,"dd"->6,"aa"->3,"tt"->2))	   
 val rdd2 = sc.parallelize(List("bb"->1,"aa"->3,"dd"->3,"ss"->6))

对于join,第一种情况,在join之前不是groupByKey,发生shuffle

val rdd1 = sc.parallelize(List("aa"->5,"bb"->5,"dd"->6,"aa"->3"tt"->2),1)	   
val rdd2 = sc.parallelize(List("bb"->1,"aa"->3,"dd"->3,"ss"->6),1)
//设置两个rdd的分区都是1,相同,
val joind: RDD[(String, (Int, Int))] = rdd1.join(rdd2)

通过UI界面可以观察到,一共有三个stage,在join 时进行了shuffle
在这里插入图片描述

#第二种情况,分区相同,join之前进行groupByKey
 val rdd1 = sc.parallelize(List("aa"->5,"bb"->5,"dd"->6,"aa"->3"tt"->2),2)	   
 val rdd2 = sc.parallelize(List("bb"->1,"aa"->3,"dd"->3,"ss"->6),2)
 //设置两个rdd的分区相同,在进行join之前使用groupBykey()
 val gb1 = rdd1.groupByKey()
 val gb2 = rdd2.groupByKey()
 val joind: RDD[(String, (Int, Int))] = gb1.join(gb2)

通过UI界面观察到,一个共有3个stage,虽然使用了两个shuffle算子,但是和使用一个join的stage相同
从平行化到groupByKey是一个stage,这样就是两个stage,groupByKey到saveAsTextFile是一个stage
rdd1经过groupByKey后,假设两个分区分别是P1:(aa,5)(aa,3) ,P2:(bb,5),(dd,6),(tt,2)
rdd2经过groupByKey后,假设两个分区分别是P1:(aa,3), P2:(bb,1),(dd,3).(ss,6)
在进行join时按key 进行join,不会发生shuffle
在这里插入图片描述

#第三种情况,两个rdd的分区数不同,在join之前进行groupByKey
 val rdd1 = sc.parallelize(List("aa"->5,"bb"->5,"dd"->6,"aa"->3"tt"->2),1)	   
 val rdd2 = sc.parallelize(List("bb"->1,"aa"->3,"dd"->3,"ss"->6),5)
 //设置两个rdd的分区不同相同,在进行join之前使用groupBykey()
 //注:在分区不同时进行join,最后在join时的分区,和rdd分区的数相同
 //这种情况下一共 会 产生1+1+5+5个task
 val gb1 = rdd1.groupByKey()
 val gb2 = rdd2.groupByKey()
 val joind: RDD[(String, (Int, Int))] = gb1.join(gb2)

有stage的图可以看出来,分区数小的rdd,在join时发生shuffle,
分区数大的rdd,在join时没有发生shuffle
在这里插入图片描述

#第四种情况,两个rdd的分区数不同,在join之前进行groupByKey,在join时从新指定分区
val rdd1 = sc.parallelize(List("aa"->5,"bb"->5,"dd"->6,"aa"->3"tt"->2),1)	   
 val rdd2 = sc.parallelize(List("bb"->1,"aa"->3,"dd"->3,"ss"->6),5)
 //设置两个rdd的分区不同相同,在进行join之前使用groupBykey()
 //注:在分区不同时进行join,最后在join时的分区,和rdd分区的数相同

 val gb1 = rdd1.groupByKey()
 val gb2 = rdd2.groupByKey()
 val joind: RDD[(String, (Int, Int))] = gb1.join(gb2,6)
// val joind: RDD[(String, (Int, Int))] = gb1.join(gb2,5)
 //val joind: RDD[(String, (Int, Int))] = gb1.join(gb2,6)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

### 区别 宽依赖和窄依赖是分布式计算框架(如 Apache Spark)中用于描述 RDD(弹性分布式数据集)之间依赖关系的重要概念。这些依赖关系决定了任务的执行方式和数据的传输路径。 - **窄依赖**是指父 RDD 的每个分区只被子 RDD 的一个分区所使用,子 RDD 分区通常对应常数个父 RDD 分区。具体来说,窄依赖分为两种情况:一个子 RDD 分区对应一个父 RDD 分区(如 map、filter 等算子),以及一个子 RDD 分区对应多个父 RDD 分区(如协同划分过的 Join)[^2]。 - **宽依赖**则是指父 RDD 的每个分区都可能被多个子 RDD 分区所使用,子 RDD 分区通常对应所有的父 RDD 分区。宽依赖也分为两种情况:一个父 RDD 对应所有子 RDD 分区(未经协同划分的 Join)或者一个父 RDD 对应非全部的多个 RDD 分区(如 groupByKey)[^2]。 ### 优势 窄依赖相对于宽依赖有两个主要优势: 1. **性能优势**:宽依赖产生 shuffle,会跨网络拉取数据,而窄依赖在一个节点内就可以完成转换。 2. **容错优势**:当子 RDD 需要重算的候,宽依赖会将所有父 RDD 的数据重算一遍,这样会出现多余计算的情况,而窄依赖,子 RDD 需要重算只需要重算对应的一个父 RDD 即可。 ### 应用场景 在分布式计算中,宽依赖和窄依赖的应用场景各有同: - **窄依赖**适用于那些需要跨节点交换大量数据的操作,例如 map、filter 和协同划分过的 Join。这些操作可以在单个节点上完成,需要进行数据 shuffle,从而减少了网络传输的成本。 - **宽依赖**则适用于那些需要跨节点重新分配数据的操作,例如 groupByKey、reduceByKey 和未经协同划分的 Join。这些操作通常会导致数据的 shuffle,即在集群中交换分区[^3]。 ### 示例代码 以下是一个简单的示例,展示了如何在 Spark 中使用窄依赖和宽依赖的操作: ```python from pyspark import SparkConf, SparkContext # 初始化 Spark 上下文 conf = SparkConf().setAppName("DependencyExample") sc = SparkContext(conf=conf) # 创建一个 RDD data = sc.parallelize([1, 2, 3, 4, 5]) # 使用窄依赖操作 narrow_dependent = data.map(lambda x: x * 2).filter(lambda x: x > 5) # 使用宽依赖操作 wide_dependent = data.groupByKey() # 收集结果并打印 print("Narrow Dependency Result:", narrow_dependent.collect()) print("Wide Dependency Result:", wide_dependent.collect()) ``` ###
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值