Spark(二):Spark的RDD
Spark 程序,一般都包含一个Driver Program用于运行main函数,在该函数中执行着各种各样的并行操作。其中在Spark中有重要的概念RDD。该RDD是一个带有分区的分布式数据集,将数据分布存储在Spark集群的各个节点。当对RDD做任何操作,该操作都是并行的。
RDD特点
RDD Represents an immutable, partitioned collection of elements that can be operated on in parallel.
RDD 代表者一个不可变、带有分区的集合,可以被并行操作。
五大特性
- A list of partitions(带有分区)
- A function for computing each split(每个分区都是独立运行function操作的,继而实现并行)
- A list of dependencies on other RDDs(因为RDD是不可变的,因此RDD存在一种转换依赖关系,将这种转换依赖关系成为RDD的血统-lineage,可以实现RDD的故障恢复)
- Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)(可以对Key-Value类型的数据,指定Partitioner策略,默认系统使用Hash-Partitioned)
- Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)(Spark在计算HDFS的时候,可以考虑最优计算策略。)
RDD编程
1、编程模型
在Spark中,RDD被表示为对象,通过对象上的方法调用来对RDD进行转换。经过一系列的transformations定义RDD之后,就可以调用actions触发RDD的计算,action可以是向应用程序返回结果(count, collect等),或者是向存储系统保存数据(saveAsTextFile等)。在Spark中,只有遇到action,才会执行RDD的计算(即延迟计算),这样在运行时可以通过管道的方式传输多个转换。
要使用Spark,开发者需要编写一个Driver程序,它被提交到集群以调度运行Worker,如下图所示。Driver中定义了一个或多个RDD,并调用RDD上的action,Worker则执行RDD分区计算任务。
2、RDD的创建
在Spark中创建RDD的创建方式可以分为三种:从集合中创建RDD;从外部存储创建RDD;从其他RDD创建。
① 从集合中创建RDD,Spark主要提供了两种函数:parallelize和makeRDD
1)使用parallelize()从集合创建
val rdd = sc.parallelize(Array(1,2,3,4,5,6,7,8))
2)使用makeRDD()从集合创建
val rdd1 = sc.makeRDD(Array(1,2,3,4,5,6,7,8))
makeRDD()底层依然调用了parallelize(),本质两种方式是一样的
② 由外部存储系统的数据集创建 【从hdfs,本地文件 读取数据创建RDD】
val rdd2 = sc.textFile("hdfs://spark1:9000/a.txt")
③ 从其他RDD创建(转换算子)
3、RDD的转换(Transformations)算子
Value类型
map(func)案例
作用:返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成
需求:创建一个1-10数组的RDD,将所有元素*2形成新的RDD
//1.创建一个RDD
val rdd1: RDD[Int] = sc.makeRDD(1 to 10)
//2.将所有元素*2
val rdd2: RDD[Int] = rdd1.map(_*2)
//3.在驱动程序中,以数组的形式返回数据集的所有元素
val arr: Array[Int] = rdd2.collect()
//4.输出数组中的元素
arr.foreach(println)
mapPartitions(func)案例
作用:类似于map,但独立地在RDD的每一个分区上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]。假设有N个元素,有M个分区,那么map的函数的将被调用N次,而mapPartitions被调用M次,一个函数一次处理所有分区。
需求:创建一个RDD,使每个元素*2组成新的RDD
//1.创建一个RDD
val rdd1: RDD[Int] = sc.makeRDD(1 to 10)
//2.将所有元素*2
val rdd2: RDD[Int] = rdd1.mapPartitions(iter => {
iter.map(_ * 2)
})
//3.打印结果
rdd2.collect().foreach(println)
map和mapPartition的区别
map():每次处理一条数据。
mapPartition():每次处理一个分区的数据
mapPartitionsWithIndex(func)案例
作用:类似于mapPartitions,但func带有一个整数参数表示分区的索引值,因此在类型为T的RDD上运 行时,func的函数类型必须是(Int, Interator[T]) => Iterator[U];
需求:创建一个RDD,使每个元素跟所在分区形成一个元组组成一个新的RDD
//1.创建一个RDD
val rdd1: RDD[Int] = sc.makeRDD(1 to 10)
//2.使每个元素跟所在分区形成一个元组组成一个新的RDD
val rdd2: RDD[(Int, Int)] = rdd1.mapPartitionsWithIndex((index, iter) =>
{
iter.map((index, _))
})
//3.打印新的RDD
rdd2.collect().foreach(println)
flatMap(func)案例
作用:类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)
需求:创建一个元素为1-5的RDD,运用flatMap创建一个新的RDD,新的RDD为(1,1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5)
//1.创建一个RDD
val rdd1: RDD[Int] = sc.makeRDD(1 to 5)
//2.根据原RDD创建新RDD
val rdd2 = rdd1.flatMap(v => 1 to v)
//3.打印新的RDD
rdd2.collect().foreach(v=>{
print(v + " ")
})
glom案例
作用:将每一个分区形成一个数组,形成新的RDD类型是RDD[Array[T]]
需求:创建一个4个分区的RDD,并将每个分区的数据放到一个数组
//1.创建一个RDD
val rdd1: RDD[Int] = sc.makeRDD(1 to 16,4)
//2.根据原RDD创建新RDD
val rdd2: RDD[Array[Int]] = rdd1.glom()
//3.打印
rdd2.collect().foreach(v=>{
println(v.mkString(","))
})
//每个分区输出一个数组,保存当前分区数据
应用场景:计算RDD中的最大值
groupBy(func)案例
作用:分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。
需求:创建一个RDD,按照元素模以2的值进行分组。
//1.创建一个RDD
val rdd1: RDD[Int] = sc.makeRDD(1 to 10)
//2.根据原RDD创建新RDD
val rdd2: RDD[(Int, Iterable[Int])] = rdd1.groupBy(v=>v%2)
//3.打印
rdd2.collect().foreach(println)
filter(func)案例
作用:过滤。返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成。
需求:创建一个RDD(由字符串组成),过滤出一个新RDD(包含”xiao”子串)
//1.创建一个RDD
val rdd1: RDD[String] = sc.makeRDD(List("xiaoming","zhangsan","xiaohong","lisi","wangxiao"))
//2.根据原RDD创建新RDD
val rdd2: RDD[String] = rdd1.filter(v=>v.contains("xiao"))
//3.打印
rdd2.collect().foreach(println)
distinct案例
作用:对源RDD进行去重后返回一个新的RDD
需求:创建一个RDD,使用distinct()对其去重。
//1.创建一个RDD
val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,3,5,6,7,1,2,3))
//2.根据原有RDD去重,产生新的RDD
val rdd2: RDD[Int] = rdd1.distinct()
//3.打印
rdd2.collect().foreach(println)
coalesce(numPartitions)案例
作用:改变分区数,用于大数据集过滤后,提高小数据集的执行效率。
当改变后分区数小于原分区数,直接调用传入分区数即可。此时减少分区的原理是将多出来的分区随机合并,不触发shuffle。默认shuffle为false
当改变后分区数大于原分区数,需要当shuffle的值设置为true,调用传入分区数即可
rdd.coalesce(6,true)
。此时增加分区的原理是重新洗牌,触发shuffle。
需求:创建一个4个分区的RDD,对其缩减分区
//1.创建一个RDD
val rdd1: RDD[