Spark核心编程
RDD:
RDD的概念: RDD(Resilient Distributed Dataset)叫做 弹性 分布式 数据集,是 Spark 中最基本的数据处理模型。
代码中是一个抽象类,它代表一个弹性的、 不可变、可分区、里面的元素可并行 计算的集合。
➢ 弹性
⚫ 存储的弹性:内存与磁盘的自动切换;
⚫ 容错的弹性:数据丢失可以自动恢复;
⚫ 计算的弹性:计算出错重试机制;
⚫ 分片的弹性:可根据需要重新分片。
➢ 分布式:数据存储在大数据集群不同节点上
➢ 数据集:RDD 封装了计算逻辑,并不保存数据 ➢ 数据抽象:RDD 是一个抽象类,需要子类具体实现
➢ 不可变:RDD 封装了计算逻辑,是不可以改变的,想要改变,只能产生新的 RDD,在 新的 RDD 里面封装计算
逻辑
➢ 可分区、并行计算
ps:其实所谓的RDD就是创建一个RDD后使用了该RDD的转换算子后,就变成了一个新的RDD。(类似于一个嵌套的样子)实例如下:
RDD 的创建:
1)从集合(内存)中创建RDD,有两个方法(parallelize和makeRDD)示例如下:
val rdd1 = sparkContext.parallelize( List(1,2,3,4) )
val rdd2 = sparkContext.makeRDD( List(1,2,3,4) )
ps:makeRDD方法其底层就是用的parallelize方法,只不过是为了编程者的方便!
2)从外部存储(文件)创建RDD, 由外部存储系统的数据集创建 RDD 包括:本地的文件系统,所有 Hadoop 支持的数据集, 比如 HDFS、HBase 等。
val fileRDD: RDD[String] = sparkContext.textFile("input")
3) 从其他 RDD 创建,主要是通过一个 RDD 运算完后,再产生新的 RDD。(如RDD的ps一般,使用最多)
4) 直接创建 RDD(new)
RDD 并行度与分区:
默认情况下,Spark 可以将一个作业切分多个任务后,发送给 Executor 节点并行计算,而能 够并行计算的任务数量我们称之为并行度。这个数量可以在构建 RDD 时指定。记住,这里 的并行执行的任务数量,并不是指的切分任务的数量,不要混淆了。
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("spark")
val sparkContext = new SparkContext(sparkConf)
val dataRDD: RDD[Int] = sparkContext.makeRDD( List(1,2,3,4), 4)
val fileRDD: RDD[String] = sparkContext.textFile( "input", 2) fileRDD.collect().foreach(println) sparkContext.stop()
RDD 转换算子:
RDD 根据数据处理方式的不同将算子整体上分为 Value 类型、双 Value 类型和 Key-Value 类型。
-
Value类型就是一个个数据。例如:List(1,2,3,4,5)
-
双Value类型就是有两个数据集的关联操作,就叫双value。例如:两个List,做交并补。
-
Key-Value类型就是两个数据为一个个体,然后有多组key-value。例如List(List(1,“hh”),List(2,“xixi”))。
1)map
➢ 函数说明: 将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。
val dataRDD1: RDD[Int] = dataRDD.map(
num => {
num * 2
}
)
2)mapPatitions
➢ 函数说明: 将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处 理,哪怕是过滤数据。(比map多一个分区的概念)
val dataRDD1: RDD[Int] = dataRDD.mapPartitions(
datas => {
datas.filter(_==2)
}
)
思考一个问题:map 和 mapPartitions 的区别?
➢ 数据处理角度:
Map 算子是分区内一个数据一个数据的执行,类似于串行操作。而 mapPartitions 算子 是以分区为单位进行批处理操作。
➢ 功能的角度:
Map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。 MapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变, 所以可以增加或减少数据
➢ 性能的角度:
Map 算子因为类似于串行操作,所以性能比较低,而是 mapPartitions 算子类似于批处 理,所以性能较高。但是 mapPartitions 算子会长时间占用内存,那么这样会导致内存可能 不够用,出现内存溢出的错误。所以在内存有限的情况下,不推荐使用。使用 map 操作。
自己的理解:map是一个一个传递过来执行,但是mapPartitions是以一个区传递过来的,然后在一个区执行完后,并不会释放内存,而是等第二个区数据过来后执行完全部分区才会释放(占内存)。
3) mapPartitionsWithIndex
➢ 函数说明: 将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
val dataRDD1 = dataRDD.mapPartitionsWithIndex(
(index, iter) => {
iter.map(
num => {
(index,num)
}
)
}
)
//查看每个数据在哪个分区(分区,数据)
4)flatMap
➢ 函数说明:将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
扁平化:整体拆分成个体
val dataRDD = sc.makeRDD(List(
List(1,2),List(3,4),2
),1)
val dataRDD1 = dataRDD.flatMap(
list =>{
list match {
case list: List[_] => list
case num => List(num)
}
}
)
dataRDD1.collect().foreach(println)
//比如有些是整体有些是个体,可以用模式匹配把个体变成整体。
5)glom
➢ 函数说明:将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
和flatMap作用相反,将个体变成整体。
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4
),1)
val dataRDD1:RDD[Array[Int]] = dataRDD.glom()
//将一个list集合先转换成int,然后再变成一个Array集合类型。
6) groupBy
➢ 函数说明:将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为 shuffle。极限情况下,数据可能被分在同一个分区中一个组的数据在一个分区中,但是并不是说一个分区中只有一个组
val dataRDD = sparkContext.makeRDD(List(1,2,3,4),1)
val dataRDD1 = dataRDD.groupBy(
_%2
)
//groupBy方法里传递一个规则,规则可以是一个方法,也可以是规则,groupBy不会跨分区。最后返回的数据是
(规则,迭代器)如下:
7)filter
➢ 函数说明:将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。 当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4
),1)
val dataRDD1 = dataRDD.filter(_%2 == 0)
//filter会匹配所有的数据,当满足条件的才会留下来
8) sample
➢ 函数说明: 根据指定的规则从数据集中抽取数据
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)
9)distinct
➢ 函数说明: 将数据集中重复的数据去重
底层源码实现:
先用map转换格式(数据,null),然后reduceByKey聚合,变成(数据,(null,null,null,......)),然后再变成(数据,null),最后再map转换格式只取第一个数据。
代码:
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),1)
val dataRDD1 = dataRDD.distinct()
val dataRDD2 = dataRDD.distinct(2)
10) coalesce
➢ 函数说明: 根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率 当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少分区的个数,减小任务调度成本。
白话文(将数据筛选的时候,原本的数据很大分了很多的分区,然后筛选后有很多个分区,但是分区里面的数据很少,这个时候就要将分区”合并“,减小任务调度的压力。)
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),6)
val dataRDD1 = dataRDD.coalesce(2)
//coalese方法的参数有两个,第一个为想要变成几个分区,第二个参数为是否打乱分区并执行shuffle,如果不填的话,默认情况为false。
思考一个问题:我想要扩大分区,怎么办?
还是使用的coalesce这个方法, 但是!注意!一定要进行shuffle操作,也就是说第二个参数一定要变成true,才能扩大分区,还有一个方法就是使用repartition。其底层就是用的coalesce。只不过repartition方法,一定会进行shuffle操作。
11)repartition
➢ 函数说明: 该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true。无论是将分区数多的 RDD 转换为分区数少的 RDD,还是将分区数少的 RDD 转换为分区数多的 RDD,repartition 操作都可以完成,因为无论如何都会经 shuffle 过程。
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),2)
val dataRDD1 = dataRDD.repartition(4)
//repartition在第10个的思考题有详细的解释!
12) sortBy
➢ 函数说明: 该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理 的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原 RDD 的分区数一 致。中间存在 shuffle 的过程
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),2)
val dataRDD1 = dataRDD.sortBy(num=>num, false, 4)
//三个参数,第一个为按照什么排序,第二个为升序还是降序(默认为升序排序),第三个为分区数。