RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。
在之前学习MR的过程中对数据是没有进行抽象的,而在Spark中对数据进行了抽象,提供一些列处理方法也就是说RDD(弹性分布式数据集),Spark计算的基石,为用户屏蔽了底层对数据的复杂抽象和处理,为用户提供了一组方便的数据转换与求值方法。
现在开发的过程中都是面向对象的思想,那么我们创建类的时候会对类封装一些属性和方法,那么创建出来的对象就具备着这些属性和方法,类也属于对数据的抽象,而Spark中的RDD就是对操作数据的一个抽象
查看原码可以得知,而且在类中提供了很多方法可以使用
弹性
存储的弹性:内存与磁盘的自动切换;
容错的弹性:数据丢失可以自动恢复;
计算的弹性:计算出错重试机制;
分片的弹性:可根据需要重新分片。
分布式:数据存储在大数据集群不同节点上
数据集:RDD封装了计算逻辑,并不保存数据
数据抽象:RDD是一个抽象类,需要子类具体实现
不可变:RDD封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD里面封装计算逻辑
可分区、并行计算
总结:
在 Spark 中,对数据的所有操作不外乎创建 RDD、转化已有RDD 以及调用 RDD 操作进行求值。每个 RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。RDD 可以包含 Python、Java、Scala 中任意类型的对象, 甚至可以包含用户自定义的对象。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。
从计算的角度来讲,数据处理过程中需要计算资源(内存 & CPU)和计算模型(逻辑)。执行时,需要将计算资源和计算模型进行协调和整合。
Spark框架在执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个的计算任务。然后将任务发到已经分配资源的计算节点上, 按照指定的计算模型进行数据计算。最后得到计算结果。
RDD是Spark框架中用于数据处理的核心模型,例如在SparkShell,执行如下命令:
sc.textFile(“xx").flatMap(_.split("")).map((_,1)).reduceByKey(_+_).saveAsTextFile(“xx")
总结:
从以上流程可以看出RDD在整个流程中主要用于将逻辑进行封装,RDD的创建->RDD的转换(转换过程中为了减少数据计算有添加缓存)->RDD的行动(输出数据)
RDD源码中提供了说明
1)一组分片(Partition),即数据集的基本组成单位。对于RDD来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认值。默认值就是程序所分配到的CPU Core的数目。 #RDD数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。 2)一个计算每个分区的函数。Spark中RDD的计算是以分片为单位的,每个RDD都会实现compute函数以达到这个目的。compute函数会对迭代器进行复合,不需要保存每次计算的结果。 #Spark在计算时,是使用分区函数对每一个分区进行计算 3)RDD之间的依赖关系。RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算。 #RDD是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个RDD建立依赖关系 4)一个Partitioner,即RDD的分片函数。当前Spark中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有对于key-value的RDD,才会有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量。 #当数据为KV类型数据时,可以通过设定分区器自定义数据的分区 5)一个列表,存储存取每个Partition的优先位置(preferred location)。对于一个HDFS文件来说,这个列表保存的就是每个Partition所在的块的位置。按照“移动数据不如移动计算”的理念,Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。 #计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算
注意 : RDD本身是不存储数据,可以看做RDD本身是一个引用数据
自动进行内存和磁盘数据存储的切换
Spark优先把数据放到内存中,如果内存放不下,就会放到磁盘里面,程序进行自动的存储切换
基于血统的高效容错机制
在RDD进行转换和动作的时候,会形成RDD的Lineage依赖链,当某一个RDD失效的时候,可以通过重新计算上游的RDD来重新生成丢失的RDD数据。
Task如果失败会自动进行特定次数的重试
RDD的计算任务如果运行失败,会自动进行任务的重新计算,默认次数是4次。
Stage如果失败会自动进行特定次数的重试
如果Job的某个Stage阶段计算失败,框架也会自动进行任务的重新计算,默认次数也是4次。
Checkpoint和Persist可主动或被动触发
RDD可以通过Persist持久化将RDD缓存到内存或者磁盘,当再次用到该RDD时直接读取就行。也可以将RDD进行检查点,检查点会将数据存储在HDFS中,该RDD的所有父RDD依赖都会被移除。
数据调度弹性
Spark把这个JOB执行模型抽象为通用的有向无环图DAG,可以将多Stage的任务串联或并行执行,调度引擎自动处理Stage的失败以及Task的失败。
总结:
存储的弹性:内存与磁盘的
自动切换容错的弹性:数据丢失可以
自动恢复计算的弹性:计算出错重试机制
分片的弹性:根据需要重新分片
在Spark中创建RDD的创建方式可以分为四种:
1) 从集合(内存)中创建RDD
从集合中创建RDD,Spark主要提供了两个方法:parallelize和makeRDD
import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} object CreateSparkRDDDemo { def main(args: Array[String]): Unit = { //先创建SparkConf和SparkContext对象 val conf = new SparkConf().setAppName("CreateSparkRDDDemo").setMaster("local") val sc = new SparkContext(conf) //从集合(内存)中创建RDD --》 这种创建方式多用于测试使用 //makeRDD和parallelize是可以指定分区数量的,有第二个参数,默认值是2,也可以指定这个值 val rdd1: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6)) val rdd2: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6)) } }
2) 从外部存储(文件)创建RDD
由外部存储系统的数据集创建RDD包括:本地的文件系统,所有Hadoop支持的数据集,比如HDFS、HBase等
import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} object CreateSparkRDDDemo { def main(args: Array[String]): Unit = { //先创建SparkConf和SparkContext对象 val conf = new SparkConf().setAppName("CreateSparkRDDDemo").setMaster("local") val sc = new SparkContext(conf) //2.从外部存(文件)创建RDD val rdd3: RDD[String] = sc.textFile("hdfs://qianfeng01:9820/word.txt") } }
3) 从其他RDD创建
主要是通过一个RDD运算完后,再产生新的RDD
import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} object CreateSparkRDDDemo { def main(args: Array[String]): Unit = { //先创建SparkConf和SparkContext对象 val conf = new SparkConf().setAppName("CreateSparkRDDDemo").setMaster("local") val sc = new SparkContext(conf) val rdd3: RDD[String] = sc.textFile("hdfs://qianfeng01:9820/word.txt") //3.从其他的RDD创建 val rdd4: RDD[String] = rdd3.flatMap(_.split(" ")) } }
4)直接创建RDD(new)
使用new的方式直接构造RDD,一般由Spark框架自身使用。
RDD的具体实现类有几十种(大概60+),介绍下最常见的几种:
源数据RDD:
spark支持读取不同的数据源,如下例子:
支持hdfs文件读取, HadoopRDD
支持jdbc读取数据库,JdbcRDD
MapPartitionsRDD
MapPartitionsRDD对于父RDD的依赖类型只能是OneToOneDependency,代表将函数应用到每一个分区的计算。
相关transformation:map, flatMap, filter, mapPartitions 等
ShuffledRDD
对于父RDD的依赖类型只能是ShuffleDependency,代表需要改变分区方式进行shuffle的计算。
会创建ShuffledRDD的transformation:reduceByKey, sortByKey 等
默认情况下,Spark可以将一个作业切分多个任务后,发送给Executor节点并行计算,而能够并行计算的任务数量我们称之为并行度。这个数量可以在构建RDD时指定。记住,这里的并行执行的任务数量,并不是指的切分任务的数量,不要混淆了。
RDD中的所有转换都是延迟加载的,也就是说,它们并不会直接计算结果。相反的,它们只是记住这些应用到基础数据集(例如一个文件)上的转换动作。只有当发生一个要求返回结果给Driver的动作时,这些转换才会真正运行。这种设计让Spark更加有效率地运行。
RDD支持两种操作:转换操作(Transformation)和行动操作(Action),RDD的转换操作是返回一个新的RDD的操作,比如map和 flatMap,而行动操作则是向Driver返回结果或将结果写出到外部存在设备,比如,collect和saveAsTextFile
列举部分算子:
转换 | 含义 |
---|---|
map(func) | 返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成 |
filter(func) | 返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成 |
flatMap(func) | 类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素) |
mapPartitions(func) | 类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U] |
mapPartitionsWithIndex(func) | 类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Iterator[T]) => Iterator[U] |
sample(withReplacement, fraction, seed) | 根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子 |
union(otherDataset) | 对源RDD和参数RDD求并集后返回一个新的RDD |
intersection(otherDataset) | 对源RDD和参数RDD求交集后返回一个新的RDD |
distinct([numTasks])) | 对源RDD进行去重后返回一个新的RDD |
groupByKey([numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD |
reduceByKey(func, [numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置 |
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) | 相同的Key值进行聚合操作,在聚合过程中同样使用了一个中立的初始值zeroValue:中立值,定义返回value的类型,并参与运算seqOp:用来在同一个partition中合并值combOp:用来在不同partiton中合并值 |
sortByKey([ascending], [numTasks]) | 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD |
sortBy(func,[ascending], [numTasks]) | 与sortByKey类似,但是更灵活 |
join(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD |
cogroup(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD |
cartesian(otherDataset) | 笛卡尔积 |
pipe(command, [envVars]) | 将一些shell命令用于Spark中生成新的RDD |
coalesce(numPartitions) | 重新分区 |
repartition(numPartitions) | 重新分区 |
repartitionAndSortWithinPartitions(partitioner) | 重新分区和排序 |
在RDD上运行计算,并返回结果给Driver或写入文件系统
动作 | 含义 |
---|---|
reduce(func) | 通过func函数聚集RDD中的所有元素,这个功能必须是可交换且可并联的 |
collect() | 在驱动程序中,以数组的形式返回数据集的所有元素 |
count() | 返回RDD的元素个数 |
first() | 返回RDD的第一个元素(类似于take(1)) |
take(n) | 返回一个由数据集的前n个元素组成的数组 |
takeSample(withReplacement,num, [seed]) | 返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子 |
takeOrdered(n, [ordering]) | takeOrdered和top类似,只不过以和top相反的顺序返回元素 |
saveAsTextFile(path) | 将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本 |
saveAsSequenceFile(path) | 将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。 |
saveAsObjectFile(path) | |
countByKey() | 针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。 |
foreach(func) | 在数据集的每一个元素上,运行函数func进行更新。 |
转换算子实战
给定数据如下: 班级ID 姓名 年龄 性别 科目 成绩 12 张三 25 男 chinese 50 12 张三 25 男 math 60 12 张三 25 男 english 70 12 李四 20 男 chinese 50 12 李四 20 男 math 50 12 李四 20 男 english 50 12 王芳 19 女 chinese 70 12 王芳 19 女 math 70 12 王芳 19 女 english 70 13 张大三 25 男 chinese 60 13 张大三 25 男 math 60 13 张大三 25 男 english 70 13 李大四 20 男 chinese 50 13 李大四 20 男 math 60 13 李大四 20 男 english 50 13 王小芳 19 女 chinese 70 13 王小芳 19 女 math 80 13 王小芳 19 女 english 70 需求如下: 1. 一共有多少人参加考试? 1.1 一共有多少个小于20岁的人参加考试? 1.2 一共有多少个等于20岁的人参加考试? 1.3 一共有多少个大于20岁的人参加考试? 1. 一共有多个男生参加考试? 2.1 一共有多少个女生参加考试? 2. 12班有多少人参加考试? 2.1 13班有多少人参加考试? 3. 语文科目的平均成绩是多少? 3.1 数学科目的平均成绩是多少? 3.2 英语科目的平均成绩是多少? 4. 单个人平均成绩是多少? 5. 12班平均成绩是多少? 5.1 12班男生平均总成绩是多少? 5.2 12班女生平均总成绩是多少? 5.3 同理求13班相关成绩 6. 全校语文成绩最高分是多少? 6.1 12班语文成绩最低分是多少? 6.2 13班数学最高成绩是多少?
代码实现:
package com.qianfeng.sparkcore
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* 统计学生相关信息
* 班级ID 姓名 年龄 性别 科目 成绩
12 张三 25 男 chinese 50
12 张三 25 男 math 60
*/
object Demo03_Stu {
def main(args: Array[String]): Unit = {
//1、获取spark上下文环境 local[n] : n代表cpu核数,*代表可用的cpu数量;如果打包服务器运行,则需要注释掉.setMaster()
val conf = new SparkConf().setAppName("spark-rdd").setMaster("local[*]")
val sc = new SparkContext(conf)
//初始化数据
val stuRDD:RDD[Stu] = sc.textFile("/Users/liyadong/data/sparkdata/stu.txt")
.filter(_.length > 0)
.map(line => {
val fields = line.split(" ")
//封装返回
Stu(
fields(0).trim.toInt,
fields(1).trim,
fields(2).trim.toInt,
fields(3).trim,
fields(4).trim,
fields(5).trim.toDouble
)
})
//打印原始数据
stuRDD.foreach(println(_))
//基于stuRDD统计需求
//q1:参加考试的人数
val totalStus = stuRDD
.map(x => (x.classID, x.stuName)) //提取学生的唯一标识数据
.distinct() //去重
.count() //统计条数
println(s"总共 $totalStus 个同学参加考试")
val totalLess20_Stus = stuRDD
.filter(x=>x.age <= 20) //过滤小于等于20岁的同学
.map(x => (x.classID, x.stuName))
.distinct() //去重
.count() //统计条数
println(s"总共 $totalLess20_Stus 个小于20岁的同学参加考试")
//2、平均成绩
stuRDD
.filter(x=>x.subject.equals("chinese")) //过滤语文科目的数据
.map(x=>{
(x.subject,(1,x.score)) //过滤语文科目,累加科目和累加科目成绩的数据
})
.reduceByKey((x,y)=>(x._1+y._1,x._2+y._2)) //累加科目次数和科目成绩总数
.foreach(x=>{
println(x._1,x._2._2/x._2._1)
})
//3、最高最低
stuRDD
.filter(x => x.subject.equals("chinese")) //过滤语文科目的数据
.map(x => {
(x.subject, x.score) //提取科目和成绩数据数据,不封装也可以
})
.coalesce(1) //将并行度减少为1
.sortBy(x => x._2, false) //根据成绩进行到排序 ;sortBy默认升序;使用false则是降序
.foreach(println(_))
//5、关闭sc对象
sc.stop()
}
}
//封装学生考试信息
case class Stu(classID:Int,stuName:String,age:Int,sex:String,subject:String,score:Double)
Guff_hys_python数据结构,大数据开发学习,python实训项目-CSDN博客