Spark RDD概述
一.RDD算子
二.RDD的创建
三.转化算子和行动算子
一.RDD算子
Spark RDD是什么
Spark提供了一种对数据的核心抽象,称为弹性分布式数据集(Resilient Distributed Dataset,简称RDD)。这个数据集的全部或部分可以缓存在内存中,并且可以在多次计算时重用。RDD其实就是一个分布在多个节点上的数据集合。
RDD的弹性主要是指:当内存不够时,数据可以持久化到磁盘,并且RDD具有高效的容错能力。
分布式数据集是指:一个数据集存储在不同的节点上,每个节点存储数据集的一部分。
例如,将数据集(hello,world,scala,spark,love,spark,happy)存储在三个节点上,节点一存储(hello,world),节点二存储(scala,spark,love),节点三存储(spark,happy),这样对三个节点的数据可以并行计算,并且三个节点的数据共同组成了一个RDD。

分布式数据集类似于HDFS中的文件分块,不同的块存储在不同的节点上;而并行计算类似于使用MapReduce读取HDFS中的数据并进行Map和Reduce操作。Spark则包含这两种功能,并且计算更加灵活。
在编程时,可以把RDD看作是一个数据操作的基本单位,而不必关心数据的分布式特性,Spark会自动将RDD的数据分发到集群的各个节点。Spark中对数据的操作主要是对RDD的操作(创建、转化、求值)。
RDD的主要特征(面试常问)
- RDD是不可变的,但可以将RDD转换成新的RDD进行操作,但是原来的RDD没有变化。
- RDD是可分区的。RDD由很多分区组成,每个分区对应一个Task任务来执行。
- 对RDD进行操作,相当于对RDD的每个分区进行操作。
- RDD拥有一系列对分区进行计算的函数,称为算子。
- RDD之间存在依赖关系,可以实现管道化,避免了中间数据的存储。
二.RDD的创建

或makeRDD()方法将一个对象集合转化为RDD。
例如,将一个List集合转化为RDD,代码如下:
val rdd=sc.parallelize(List(1,2,3,4,5,6))
或者
val rdd=sc.makeRDD(List(1,2,3,4,5,6))
从返回信息可以看出,上述创建的RDD中存储的是Int类型的数据。实际上,RDD也是一个集合,与常用的List集合不同的是,RDD集合的数据分布于多台机器上。
从外部存储创建RDD
Spark的textFile()方法可以读取本地文件系统或外部其他系统中的数据,并创建RDD。不同的是,数据的来源路径不同。
- 读取本地系统文件
# 将读取的本地文件内容转为一个RDD
val rdd = sc.textFile("file:///root/data/words.txt")
# 使用collect()方法查看RDD中的内容
rdd.collect() # 或者使用rdd.collect
注:collect()方法是RDD的一个行动算子
- 读取HDFS系统文件
# 将读取的HDFS系统文件内容转为一个RDD
val rdd = sc.textFile("hdfs://192.168.121.131:9000/words.txt")
# 使用collect()方法查看RDD中的内容
rdd.collect() # 或者使用rdd.collect
三.RDD算子
RDD被创建后是只读的,不允许修改。Spark提供了丰富的用于操作RDD的方法,这些方法被称为算子。一个创建完成的RDD只支持两种算子:转化(Transformation)算子和行动(Action)算子。
转化算子
转化算子负责对RDD中的数据进行计算并转化为新的RDD。Spark中的所有转化算子都是惰性的,因为它们不会立即计算结果,而只是记住对某个RDD的具体操作过程,直到遇到行动算子才会与其一起执行。
- map()算子
map()是一种转化算子,它接收一个函数作为参数,并把这个函数应用于RDD的每个元素,最后将函数的返回结果作为结果RDD中对应元素的值。

val rdd1=sc.parallelize(List(1,2,3,4,5,6))
val rdd2=rdd1.map(x => x+1)
在上述代码中,向算子map()传入了一个函数x=>x+1。其中,x为函数的参数名称。Spark会将RDD中的每个元素传入该函数的参数中。也可以将参数使用下划线“_”代替。例如以下代码:
val rdd1=sc.parallelize(List(1,2,3,4,5,6))
val rdd2=rdd1.map(_+1)
上述代码中的下划线"_"代表rdd1中的每个元素。实际上rdd1和rdd2中没有任何数据,因为parallelize()和map()都为转化算子,调用转化算子不会立即计算结果。若需要查看计算结果,则可使用行动算子collect()。
- filter()算子
filter()算子通过函数对源RDD的每个元素进行过滤,并返回一个新的RDD。
例如以下代码,过滤出rdd1中大于3的所有元素,并输出结果:
val rdd1=sc.parallelize(List(1,2,3,4,5,6))
#下面这行代码等同于:val rdd2=rdd1.filter(_>3)
val rdd2=rdd1.filter(x=>x>3)
rdd2.collect
# 也可以这样:
sc.parallelize(List(1,2,3,4,5,6)).filter(x=>x>3).collect()
- flatMap()算子
与map()算子类似,但是每个传入函数的RDD元素会返回0到多个元素,最终会将返回的所有元素合并到一个RDD。
例如以下代码,将集合List转为rdd1,然后调用rdd1的flatMap()算子将rdd1的每个元素按照空格分割成多个元素,最终合并所有元素到一个新的RDD。
val rdd1=
sc.parallelize(List("hadoop hello scala","spark hello"))
# 等同于 val rdd2 = rdd1.flatMap(x=>x.split(" "))
val rdd2=rdd1.flatMap(_.split(" "))
rdd2.collect
上述代码使用flatMap()算子的运行过程如下图所示:

- reduceByKey()算子
reduceByKey()算子的作用对象是元素为(key,value)形式(Scala元组)的RDD,使用该算子可以将key相同的元素聚集到一起,最终把所有key相同的元素合并成一个元素。该元素的key不变,value可以聚合成一个列表或者进行求和等操作。最终返回的RDD的元素类型和原有类型保持一致。
例如,有两个同学zhangsan和lisi,zhangsan的语文和数学成绩分别为98、78,lisi的语文和数学成绩分别为88、79,现需要分别求zhangsan和lisi的总成绩,代码如下:
val list=List(("zhangsan",98),("zhangsan",78),("lisi",88),("lisi",79))
val rdd1=sc.parallelize(list)
val rdd2=rdd1.reduceByKey((x,y)=>x+y)
rdd2.collect
上述代码使用了reduceByKey()算子,并传入了函数(x,y)=>x+y,其中x和y代表key相同的两个value值。该算子会寻找key相同的元素,当找到这样的元素时会对其value执行(x,y)=>x+y处理,即只保留求和后的数据作为value。
整个运行过程如图所示:

此外,上述代码中的rdd1.reduceByKey((x,y)=>x+y)可以简化为以下代码:
rdd1.reduceByKey(_+_)
- groupByKey()算子
groupByKey()算子的作用对象是元素为(key,value)形式(Scala元组)的RDD,使用该算子可以将key相同的元素聚集到一起,最终把所有key相同的元素合并成为一个元素。该元素的key不变,value则聚集到一个集合中。
仍然以上述求学生zhangsan和lisi的总成绩为例,使用groupByKey()算子的代码如下:
val list=List(("zhangsan",98),("zhangsan",78),("lisi",88),("lisi",79))
val rdd1=sc.parallelize(list)
val rdd2=rdd1.groupByKey()
rdd2.map(x => (x._1,x._2.sum)).collect
从上述代码可以看出,groupByKey()相当于reduceByKey()算子的一部分。首先使用groupByKey()算子对RDD数据进行分组后,返回了元素类型为(String, Iterable[Int])的RDD,然后对该RDD使用map()算子进行函数操作,对成绩集合进行求和。
整个运行过程如图所示:

- union()算子
union()算子将两个RDD合并为一个新的RDD,主要用于对不同的数据来源进行合并,两个RDD中的数据类型要保持一致。
例如以下代码,通过集合创建了两个RDD,然后将两个RDD合并成了一个RDD:
val rdd1=sc.parallelize(Array(1,2,3))
val rdd2=sc.parallelize(Array(4,5,6))
val rdd3=rdd1.union(rdd2)
rdd3.collect
- sortBy()算子
sortBy()算子将RDD中的元素按照某个规则进行排序。该算子的第一个参数为排序函数,第二个参数是一个布尔值,指定升序(默认)或降序。若需要降序排列,则需将第二个参数置为false。
例如,一个数组中存放了三个元组,将该数组转为RDD集合,然后对该RDD按照每个元素中的第二个值进行降序排列,代码如下:
val rdd1=sc.parallelize(Array(("hadoop",12),("java",32),("spark",22)))
val rdd2=rdd1.sortBy(x=>x._2,false)
rdd2.collect
上述代码sortBy(x=>x._2,false)中的x代表rdd1中的每个元素。由于rdd1的每个元素是一个元组,因此使用x._2取得每个元素的第二个值。当然,sortBy(x=>x.2,false)也可以直接简化为sortBy(._2,false)。
- sortByKey()算子
sortByKey()算子将(key,value)形式的RDD按照key进行排序。默认升序,若需降序排列,则可以传入参数false,代码如下:
val rdd1=sc.parallelize(Array(("hadoop",12),("java",32),("spark",22)))
val rdd2=rdd1.sortByKey(false)
rdd2.collect()
- join()算子
join()算子将两个(key,value)形式的RDD根据key进行连接操作,相当于数据库的内连接(Inner Join),只返回两个RDD都匹配的内容。例如,将rdd1和rdd2进行内连接,代码如下:
val arr1=
Array(("A","a1"),("B","b1"),("C","c1"),("D","d1"),("E","e1"))
val rdd1 = sc.parallelize(arr1)
val arr2=
Array(("A","A1"),("B","B1"),("C","C1"),("C","C2"),("C","C3"),("E","E1"))
val rdd2 = sc.parallelize(arr2)
rdd1.join(rdd2).collect
rdd2.join(rdd1).collect
上述代码使用join()算子的运行过程如图所示:

除了内连接join()算子外,RDD也支持左外连接leftOuterJoin()算子、右外连接rightOuterJoin()算子、全外连接fullOuterJoin()算子。
leftOuterJoin()算子与数据库的左外连接类似,以左边的RDD为基准(例如rdd1.leftOuterJoin(rdd2),以rdd1为基准),左边RDD的记录一定会存在。对上述rdd1和rdd2进行左外连接,代码如下:
rdd1.leftOuterJoin(rdd2).collect
rdd2.leftOuterJoin(rdd1).collect
上述代码使用leftOuterJoin()算子的运行过程如图所示:

rightOuterJoin()算子的使用方法与leftOuterJoin()算子相反,其与数据库的右外连接类似,以右边的RDD为基准(例如rdd1.rightOuterJoin(rdd2),以rdd2为基准),右边RDD的记录一定会存在。
fullOuterJoin()算子与数据库的全外连接类似,相当于对两个RDD取并集,两个RDD的记录都会存在。
对上述rdd1和rdd2进行全外连接,代码如下:
rdd1.fullOuterJoin(rdd2).collect
rdd2.fullOuterJoin(rdd1).collect
上述代码使用fullOuterJoin()算子的运行过程如图所示:

- intersection()算子
intersection()算子对两个RDD进行取交集操作,返回一个新的RDD,代码如下:
val rdd1 = sc.parallelize(1 to 5)
val rdd2 = sc.parallelize(3 to 7)
rdd1.intersection(rdd2).collect
- distinct()算子
distinct()算子对RDD中的数据进行去重操作,返回一个新的RDD,代码如下:
val rdd = sc.parallelize(List(1,2,3,3,4,2,1))
rdd.distinct.collect # 或 rdd.distinct().collect()
行动算子
Spark中的转化算子并不会马上进行运算,而是在遇到行动算子时才会执行相应的语句,触发Spark的任务调度。Spark常用的行动算子及其介绍如表所示。

- reduce()算子
将数字1~100所组成的集合转为RDD,然后对该RDD使用reduce()算子进行计算,统计RDD中所有元素值的总和,代码如下:
val rdd1 = sc.parallelize(1 to 100)
rdd1.reduce(_+_)
- count()算子
统计RDD集合中元素的数量,代码如下:
val rdd1 = sc.parallelize(1 to 100)
rdd1.count
- countByKey()算子
List集合中存储的是键值对形式的元组,使用该List集合创建一个RDD,然后对其使用countByKey()算子进行计算,代码如下:
val rdd1 = sc.parallelize(List(("zhang",87),("zhang",79),("li",90)))
rdd1.countByKey
- take(n)算子
返回集合中前5个元素组成的数组,代码如下:
val rdd1 = sc.parallelize(1 to 100)
rdd1.take(5)
2717

被折叠的 条评论
为什么被折叠?



