Spark系列文章:
大数据 - Spark系列《一》- 从Hadoop到Spark:大数据计算引擎的演进-CSDN博客
大数据 - Spark系列《二》- 关于Spark在Idea中的一些常用配置-CSDN博客
大数据 - Spark系列《三》- 加载各种数据源创建RDD-CSDN博客
大数据 - Spark系列《四》- Spark分布式运行原理-CSDN博客
大数据 - Spark系列《五》- Spark常用算子-CSDN博客
大数据 - Spark系列《六》- RDD详解-CSDN博客
目录
7.1 什么是分区器?
7.2 RDD之间的依赖关系
1. 窄依赖
2. 宽依赖
7.3 什么时候需要使用分区器
7.4 内置分区器
1.HashPartitioner(哈希分区器)
2.RangePartitioner(范围分区器)
7.5 自定义分区器
分区器是上下游RDD分配数据的规则
RDD之间存在依赖关系,可以通过RDD之间的依赖关系容错恢复 !
rdd之间的依赖关系又称为血源关系
依赖关系分两种: (根据上下游RDD之间数据的传输规则)
上游RDD的任意一个分区的数据只会被下游某一个分区引用
上游RDD的任意一个分区的数据可能会被下游所有分区引用 ---shuffle 需要分区器
package com.doit.day0217
import org.apache.spark.rdd.{JdbcRDD, RDD}
import org.apache.spark.{SparkConf, SparkContext}
import java.sql.{DriverManager, ResultSet}
import org.apache.log4j.{Level, Logger}
/**
* @日期: 2024/2/19
* @Author: Wang NaPao
* @Blog: https://blog.csdn.net/weixin_40968325?spm=1018.2226.3001.5343
* @Tips: 和我一起学习吧
* @Description:
*/
object Test04 {
def main(args: Array[String]): Unit = {
// 创建SparkConf对象,并设置应用程序名称和运行模式
val conf = new SparkConf()
.setAppName("Starting...") // 设置应用程序名称
.setMaster("local[*]") // 设置运行模式为本地模式
// 创建SparkContext对象,并传入SparkConf对象
val sc = new SparkContext(conf)
val rdd1 = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 2) // 创建一个有两个分区的RDD
// 对rdd1的每个元素乘以10,这是一个窄依赖,因为每个rdd1分区的数据仅影响对应rdd2分区的数据
val rdd2 = rdd1.map(_ * 10)
// 过滤出rdd2中大于20的元素,这也是一个窄依赖,因为每个rdd2分区的数据仅影响对应rdd3分区的数据
val rdd3 = rdd2.filter(_ > 20)
// 根据元素值对rdd3进行分组,这是一个宽依赖,因为每个rdd3分区的数据可能会影响多个rdd4分区的数据
val rdd4 = rdd3.groupBy(e => e)
// 对rdd4的每个分区进行处理,并输出分区号和分区中的元素
val result2 = rdd4.mapPartitionsWithIndex((p, iter) => {
iter.map(e => p + ": " + e)
})
result2.foreach(println)
// 关闭SparkContext对象
sc.stop()
}
}
作用: RDD之间的依赖关系--宽/窄依赖
宽依赖会Shuffle , 窄依赖不会
宽依赖需要分区器 , 窄依赖不需要
程序在执行过程中, 遇到宽依赖自动的断开任务的执行, 划分成两个阶段
程序在执行过程中 , 如果两个RDD之间是窄依赖 ,将两个RDD的计算逻辑组成算子链条
各个阶段就会以最后一个RDD的分区数创建指定个数的任务实例
阶段之间有先后关系,不同阶段对应不同的任务
宽依赖,产生shuffle的时候(上游RDD一个分区的数据可能被下游RDD的所有分区引用)
Spark 提供了几种内置的分区器,常用的包括:
工作原理:
HashPartitioner根据数据的哈希值将数据分配到不同的分区中。具体来说,对于RDD中的每个键值对(K,V),HashPartitioner会调用键的hashCode方法来计算哈希值,然后取模分区数来确定该键值对所属的分区编号。因为相同键的哈希值相同,所以相同键的数据会被分配到同一个分区中。
优点:
实现简单,计算快速。
数据均匀分布,适用于大多数场景。
缺点:
当键的分布不均匀时,可能会导致分区不均匀,进而影响性能。
工作原理:
RangePartitioner适用于有序的RDD,并根据键的范围将数据分配到不同的分区中。具体来说,RangePartitioner会根据RDD的键进行排序,并将RDD划分成连续的若干个范围(range),然后将每个范围内的数据分配到一个分区中。范围的划分依据是根据目标分区数平均划分。
优点:
适用于有序数据集,能够有效地将数据按照键的范围分配到不同的分区中。
分区间数据分布较为均匀。
缺点:
对数据集的有序要求较高,可能会增加预处理成本。
在数据不均匀的情况下,可能会导致分区不均匀,进而影响性能。
允许用户根据自己的需求定制数据的分区规则,以满足特定的业务需求。
package com.doit.day0217
import org.apache.spark.Partitioner
/**
* @日期: 2024/2/18
* @Author: Wang NaPao
* @Blog: https://blog.csdn.net/weixin_40968325?spm=1018.2226.3001.5343
* @Tips: 和我一起学习吧
* @Description:
*/
class MyPartitioner(p: Int) extends Partitioner {
// 分区数
override def numPartitions: Int = p
// 变量用于轮询选择分区编号
var x: Int = 0
// 根据键获取分区编号的方法
override def getPartition(key: Any): Int = {
// 分区规则:不同的 key 返回不同的分区编号
if (x == 0) {
x = 1
} else {
x = 0
}
x
}
}
package com.doit.day0217
import org.apache.spark.rdd.{JdbcRDD, RDD}
import org.apache.spark.{SparkConf, SparkContext}
import java.sql.{DriverManager, ResultSet}
import org.apache.log4j.{Level, Logger}
/**
* @日期: 2024/2/18
* @Author: Wang NaPao
* @Blog: https://blog.csdn.net/weixin_40968325?spm=1018.2226.3001.5343
* @Tips: 和我一起学习吧
* @Description:
*/
object Test03 {
def main(args: Array[String]): Unit = {
// 创建SparkConf对象,并设置应用程序名称和运行模式
val conf = new SparkConf()
.setAppName("Starting...") // 设置应用程序名称
.setMaster("local[*]") // 设置运行模式为本地模式
// 创建SparkContext对象,并传入SparkConf对象
val sc = new SparkContext(conf)
// 创建普通的 RDD
val rdd = sc.parallelize(List("apple", "banana", "cherry", "date", "elderberry","a","b","c"),2)
// 转换为键值对 RDD,键为字符串本身,值为字符串长度
val kvRDD = rdd.map(str => (str, str.length))
println(kvRDD.getNumPartitions)
// 使用自定义分区器进行分区
val partitionedRDD = kvRDD.partitionBy(new MyPartitioner(2))
partitionedRDD.mapPartitionsWithIndex((p, iter) => {
iter.map(e => (p, e))
}).foreach(println)
}
}