一、什么是RDD?
RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据处理模型。代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。
- 弹性
- 存储的弹性:内存与磁盘的自动切换;
- 容错的弹性:数据丢失可以自动恢复;
- 计算的弹性:计算出错重试机制;
- 分片的弹性:可根据需要重新分片。
- 分布式:数据存储在大数据集群不同节点上
- 数据集:RDD封装了计算逻辑,并不保存数据
- 数据抽象:RDD是一个抽象类,需要子类具体实现
- 不可变:RDD封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD里面封装计算逻辑
- 可分区、并行计算
二、五大核心属性
-
分区列表
RDD数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。
-
分区计算函数
Spark在计算时,是使用分区函数对每一个分区进行计算
-
RDD之间的依赖关系
RDD是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个RDD建立依赖关系
-
分区器
当数据为KV类型数据时,可以通过设定分区器自定义数据的分区。
-
首选位置
计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算。
三、执行原理
从计算的角度来讲,数据处理过程中需要计算资源(内存 & CPU)和计算模型(逻辑)。执行时,需要将计算资源和计算模型进行协调和整合。
Spark框架在执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个的计算任务。然后将任务发到已经分配资源的计算节点上, 按照指定的计算模型进行数据计算。最后得到计算结果。
RDD是Spark框架中用于数据处理的核心模型,接下来我们看看,在Yarn环境中,RDD的工作原理:
1) 启动Yarn集群环境
2) Spark通过申请资源创建调度节点和计算节点
3) Spark框架根据需求将计算逻辑根据分区划分成不同的任务
4) 调度节点将任务根据计算节点状态发送到对应的计算节点进行计算
四、基础编程
- 创建RDD
在Spark中创建RDD可以分为四种:-
从集合中创建RDD:makeRDD 和 parallelize 两种方法。
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("spark") val sc = new SparkContext(sparkConf) val rdd1 = sc.parallelize(List(1,2,3,4)) val rdd2 = sc.makeRDD(List(1,2,3,4)) rdd1.collect().foreach(println) rdd2.collect().foreach(println) sc.stop()
从底层代码实现来讲,makeRDD方法其实就是parallelize方法:
def makeRDD[T: ClassTag]( seq: Seq[T], numSlices: Int = defaultParallelism): RDD[T] = withScope { parallelize(seq, numSlices) } )
-
从外部存储创建RDD
由外部存储系统的数据集创建RDD包括:本地的文件系统,所有Hadoop支持的数据集,比如HDFS、HBase等。val sparkConf = new SparkConf().setMaster("local[*]").setAppName("spark") val sc = new SparkContext(sparkConf) val fileRDD: RDD[String] = sc.textFile("input") // 读取外部文件,可以是一个目录 fileRDD.collect().foreach(println) sc.stop()
-
从其他RDD创建
主要是通过一个RDD运算完后,再产生新的RDD。详情请参考后续章节 -
直接创建RDD(new)
使用new的方式直接构造RDD,一般由Spark框架自身使用。
-
- 转换算子
RDD根据数据处理方式的不同将算子整体上分为单Value类型、双Value类型和Key-Value类型。- 单value算子
-
map 算子
算子可以说是整个 Spark 环境最常用的算子之一,它主要是针对数据结构进行转换。这里的转换可以是类型的转换,也可以是值的转换。
函数签名:def map[U: ClassTag](f: T => U): RDD[U]
示例:
// 对集合中的每个元素进行 *2 处理 val dataRDD: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4)) val dataRDD1: RDD[Int] = dataRDD.map( num => { num * 2 } )
-
mapPartitions算子
与 map 算子类似,只不过map是针对数据逐条处理,而 mapPartitions 按照分区处理数据。
函数签名:def mapPartitions[U: ClassTag]( f: Iterator[T] => Iterator[U], preservesPartitioning: Boolean = false): RDD[U] )
示例:
// 过滤出集合中的偶数 val dataRDD1: RDD[Int] = dataRDD.mapPartitions( datas => { datas.filter(_%2 == 0) } ) // 求每个分区中的最大值 val newRDD = rdd.mapPartitions( iter => { // scala iterator max min sum List(iter.max).iterator } )
注意:由于 mapPartitions 算子是针对分区处理数据,当一个分区内的数据量过大时,可能会导致OOM(内存溢出),此时为了保证程序的运行,应使用map算子,即使会降低程序的运行效率;或者数据重新分区,打散数据
-
mapPartitionsWithIndex算子
该算子与 mapPartitions 类似,只不过在处理数据的时候可以获取分区索引。 函数签名:def mapPartitionsWithIndex[U: ClassTag]( f: (Int, Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false): RDD[U] )
示例:
// 获取第二个分区的数据 val list = List(1,2,3,4,5,6) // 分区索引编号从0开始 // 获取的数据应该编号为1 val rdd = sc.makeRDD(list, 3) // 将数据打散为三个分区 // mapPartitionsWithIndex : (index, iterator) => iterator val newRDD = rdd.mapPartitionsWithIndex( (index, iter) => { if ( index == 1 ) { iter } else { // null Nil.iterator //iter.filter(false) } } )
注意:由于该算子也是对分区数据处理,仍然可能出现OOM(内存溢出的情况)。
-
flatMap 将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射。
函数签名:def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]
示例:
val dataRDD = sparkContext.makeRDD(List(List(1,2),List(3,4)), 1) val dataRDD1 = dataRDD.flatMap( list => list ) // 将List(List(1,2),3,List(4,5))进行扁平化操作 val list = List(List(1,2),3,List(4,5)) val rdd = sc.makeRDD(list) rdd.flatMap( data => { data match { // 模式匹配,由于集合中存在不同类型的数据,需要对不同类型扁平化 case list:List[_] => list case d => List(d) } } ).collect.foreach(println)
-
groupBy
将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为shuffle。极限情况下,数据可能被分在同一个分区中。
一个组的数据在一个分区中,但是并不是说一个分区中只有一个组。
函数签名:def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
示例:
// 按奇数和偶数分组 val dataRDD = sparkContext.makeRDD(List(1,2,3,4), 1) val dataRDD1 = dataRDD.groupBy( _%2 )
-
filter 顾名思义,按规则过滤数据: 函数签名:
def filter(f: T => Boolean): RDD[T]
示例:
// 过滤出偶数 val dataRDD = sparkContext.makeRDD(List(1,2,3,4), 1) val dataRDD1 = dataRDD.filter(_%2 == 0)
注意:当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
-
sample
根据指定的规则从数据集中抽取数据。
函数签名:def sample( withReplacement: Boolean, fraction: Double, seed: Long = Utils.random.nextLong): RDD[T] )
示例:
val dataRDD = sparkContext.makeRDD(List(1,2,3,4), 1) // 抽取数据不放回(伯努利算法) // 伯努利算法:又叫0、1分布。例如扔硬币,要么正面,要么反面。 // 具体实现:根据种子和随机算法算出一个数和第二个参数设置几率比较,小于第二个参数要,大于不要 // 第一个参数:抽取的数据是否放回,false:不放回 // 第二个参数:抽取的几率,范围在[0,1]之间,0:全不取;1:全取; // 第三个参数:随机数种子 val dataRDD1 = dataRDD.sample(false, 0.5) // 抽取数据放回(泊松算法) // 第一个参数:抽取的数据是否放回,true:放回;false:不放回 // 第二个参数:重复数据的几率,范围大于等于0.表示每一个元素被期望抽取到的次数 // 第三个参数:随机数种子 val dataRDD2 = dataRDD.sample(true, 2)
该算子的作用是对大数据集进行数据抽取,检测数据倾斜的问题:
val listRDD: RDD[Int] = sc.makeRDD(List(1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 2, 1, 3, 2, 1, 3, 1, 3, 1, 3, 2)) val sampleRDD: RDD[Int] = listRDD.sample(true, 1) val result: RDD[(Int, Iterable[Int])] = sampleRDD.groupBy(data => data)
-
distinct
去重算子。
函数签名:def distinct()(implicit ord: Ordering[T] = null): RDD[T] def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
示例:
val dataRDD = sparkContext.makeRDD(List(1,2,3,4,1,2), 1) val dataRDD1 = dataRDD.distinct() val dataRDD2 = dataRDD.distinct(2) // 重新设置数据分区数,引发shuffle操作。
-
coalesce
缩减分区,用于大数据集过滤后,提高小数据集的执行效率,多配合filter使用。 函数签名:def coalesce(numPartitions: Int, shuffle: Boolean = false, partitionCoalescer: Option[PartitionCoalescer] = Option.empty) (implicit ord: Ordering[T] = null) : RDD[T]
示例:
val dataRDD = sparkContext.makeRDD(List(1,2,3,4,1,2), 6) val dataRDD1 = dataRDD.coalesce(2)
-
repartition
该操作内部其实执行的是coalesce操作,参数shuffle的默认值为true。无论是将分区数多的RDD转换为分区数少的RDD,还是将分区数少的RDD转换为分区数多的RDD,repartition操作都可以完成,因为无论如何都会经shuffle过程。
函数签名:def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
示例:
val dataRDD = sparkContext.makeRDD(List(1,2,3,4,1,2), 2) val dataRDD1 = dataRDD.repartition(4)
-
sortBy
该操作用于排序数据。在排序之前,可以将数据通过指定规则进行处理,之后按照指定规则处理的结果进行排序,默认为正序排列。默认排序后新产生的RDD的分区数与原RDD的分区数一致。
函数签名:def sortBy[K]( f: (T) => K, ascending: Boolean = true, numPartitions: Int = this.partitions.length) (implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T] )
示例:
val dataRDD = sparkContext.makeRDD(List(1,2,3,4,1,2), 2) val dataRDD1 = dataRDD.sortBy(num=>num, false, 4) // 第二个参数默认为true,升序;第三个参数默认为之前的分区数,可以手动修改分区数,引起shuffle操作。
-
- 单value算子