首先要说明RDD的算子一共分为两种一种为行动算子一种为transformations算子。
依赖
RDDs通过操作算子进行转换,转换得到的新RDD包含了从其他RDDs衍生所必需的信息,RDDs之间维护着这种血缘关系,也称之为依赖。如下图所示,依赖包括两种,一种是窄依赖,RDDs之间分区是一一对应的,另一种是宽依赖,下游RDD的每个分区与上游RDD(也称之为父RDD)的每个分区都有关,是多对多的关系。
缓存
如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用。
CheckPoint
虽然RDD的血缘关系天然地可以实现容错,当RDD的某个分区数据失败或丢失,可以通过血缘关系重建。但是对于长时间迭代型应用来说,随着迭代的进行,RDDs之间的血缘关系会越来越长,一旦在后续迭代过程中出错,则需要通过非常长的血缘关系去重建,势必影响性能。为此,RDD支持checkpoint将数据保存到持久化的存储中,这样就可以切断之前的血缘关系,因为checkpoint后的RDD不需要知道它的父RDDs了,它可以从checkpoint处拿到数据。
RDD编程
RDD的创建:在Spark中创建RDD的创建方式可以分为三种:从集合中创建RDD;从外部存储创建RDD;从其他RDD创建。
1、从集合创建:主要提供了两个函数:parallelize和makeRDD
2、由外部存储系统的数据集创建包括本地的文件系统,还有所有Hadoop支持的数据集,比如HDFS、Cassandra、HBase等,比如:sc.textFile(“”url“”)具体含义后边讲解
3、从其他RDD转换
RDD的转换(面试开发重点)API
首先其整体分为Value型和Key、Value型
Value型:
函数 | 参数说明 | 举例 |
---|---|---|
map | 略,在Scala一文中已经说过 | … |
mapPartitions(func) | 是对每一个分区进行一次map,假设有N个元素M个分区,那么map会执行N次而mapPatition则执行M次,因此其func的类型为Iterator【T】 | mapPartition |
mapPartitionsWithIndex(func) | 这个就是指定某个索引值执行func,就是比上一个多了个可以指定某个分区进行func计算 | mapPartitionsWithIndex(func) |
flatMap(func) | … | … |
glom | 将每个分区变成数组形成新的RDD类型RDD[Array【T】 | glom |
groupBy(func) | 将函数返回值相同的放入一个iterator中 | groupBy |
filter(func) | … | … |
sample(withReplacement, fraction, seed) | 抽样,第一参数为true表示放回的抽取,反之不放回抽取,第二个参数为抽样抽几个,第三个参数为随机数种子 | sample |
distinct([numTasks])) | 对Rdd进行去重操作,可以传参数来指定有几个线程来完成 | rdd.distinct(2) |
coalesce(numPartitions) | 缩减分区,由于大数据集过滤后,提高小数据执行效率 | rdd.coalesce(3) |
repartition(numPartitions) | 根据分区数,重新通过网络随机洗牌所有数据 | 他和上面的coalesce的区别 |
sortBy(func,[ascending], [numTasks]) | 将rdd用func进行排序,第二个为升序降序,第三个并行数 | sortBy |
pipe(command, [envVars]) | 管道,针对每个分区,都执行一个shell角标,返回输出的RDD | pipe |
举例:
1、mapPartition:
var sc=new SparkContext();
var rdd=sc.parallelize(Array(1,2,3,4))
rdd.mapPartition(x=>x.map(_*2))
2、mapPartitionsWithIndex(func):
创建一个RDD,使每个元素跟所在分区形成一个元组组成一个新的RDD
var sc=new SparkContext();
var rdd=sc.parallelize(Array(1,2,3,4))
rdd.mapPartitionsWithIndex((i,its)=>its.map((_,i)))
map()和mapPartition()的区别:
- map():每次处理一条数据。
- mapPartition():每次处理一个分区的数据,这个分区的数据处理完后,原RDD中分区的数据才能释放,可能导致OOM。
- 开发指导:当内存空间较大的时候建议使用mapPartition(),以提高处理效率。
3、glom
创建一个4个分区的RDD,并将每个分区的数据放到一个数组
var sc=new SparkContext();
var rdd=sc.parallelize(1 to 16,4)
rdd.glom().collect()
4、groupBy
需求:创建一个RDD,按照元素模以2的值进行分组。
var sc=new SparkContext();
var rdd=sc.parallelize(1 to 16,4)
rdd.groupBy(i=>i%2)
5、sample
需求:创建一个RDD(1-10),从中选择放回和不放回抽样
var sc=new SparkContext();
var rdd=sc.parallelize(1 to 16,4)
rdd.sample(true,0.4,2)
6、coalesce和repartition的区别
- coalesce重新分区,可以选择是否进行shuffle过程。由参数shuffle: Boolean = false/true决定。
- repartition实际上是调用的coalesce,默认是进行shuffle的。源码如下:
7、sortBy:
按照与3余数的大小排序
var sc=new SparkContext();
var rdd=sc.parallelize(1 to 16,4)
rdd.sortBy(x=>x%3).collect()
输出结果为:res12: Array[Int] = Array(3, 4, 1, 2)
8、pipe
编写一个脚本,使用管道将脚本作用于RDD上。编写一个脚本,使用管道将脚本作用于RDD上。
编写一个脚本
Shell脚本
#!/bin/sh
echo "AA"
while read LINE; do
echo ">>>"${LINE}
val rdd = sc.parallelize(List("hi","Hello","how","are","you"),1)
rdd.pipe("/opt/module/spark/pipe.sh").collect()
输出结果:
Array(AA, >>>hi, >>>Hello, >>>how, >>>are, >>>you)
双Value类型交互
函数 | 说明 | 举例 |
---|---|---|
union(otherDataset) | 对源RDD和参数RDD求并集后返回一个新的RDD | union |
subtract (otherDataset) | 求差的一个函数 | subtract |
intersection(otherDataset) | 求交集 | intersection(otherDataset) |
cartesian(otherDataset) | 求笛卡尔积两个RDD的 | … |
zip(otherDataset) | 拉链,将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常。 | zip |
1、union
创建两个RDD,求并集
//创建第一个RDD
val rdd1 = sc.parallelize(1 to 5)
//创建第二个RDD
val rdd2 = sc.parallelize(5 to 10)
//计算两个RDD的并集
val rdd3 = rdd1.union(rdd2)
rdd3的结果为:Array(1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10)
2、subtract
计算差的一种函数,去除两个RDD中相同的元素,不同的RDD将保留下来
val rdd = sc.parallelize(3 to 8)
val rdd1 = sc.parallelize(1 to 5)
rdd.subtract(rdd1).collect()
输出结果为: Array(8, 6, 7)
3、intersection(otherDataset)
val rdd1 = sc.parallelize(1 to 7)
val rdd2 = sc.parallelize(5 to 10)
val rdd3 = rdd1.intersection(rdd2)
rdd3.collect()
输出结果为:Array(5, 6, 7)
4、zip
val rdd1 = sc.parallelize(Array(1,2,3),3)
val rdd2 = sc.parallelize(Array("a","b","c"),3)
rdd1.zip(rdd2).collect
输出结果为:Array((1,a), (2,b), (3,c))
Key-Value类型
函数 | 参数说明 | 举例 |
---|---|---|
partitionBy | 对RDD进行重新分区,如果原有的RDD和现有的RDD是一致的话就不进行分区,否则生成shuffleRDD,即会产生shuffle | partitionBy |
groupByKey | 也是对每个key进行操作生成的序列(key,迭代器) | groupByKey |
reduceByKey(func, [numTasks]) | func(x,y)其中x,y就是相同的key所对应的两个value比如(1,x),(1,y) | reduceByKey |
aggregateByKey | zeroValue:给每一个分区中的每一个key一个初始值;seqOp:函数用于在每一个分区中用初始值逐步迭代value,可以理解为就是对每个分区执行的操作;)combOp:函数用于合并每个分区中的结果。 | aggregateByKey |
foldByKey | 是上面的简化版其中第二个参数和第三个参数一致了 | foldByKey |
combineByKey[C] | 其实他和aggregateByKey十分类似,只不过他的第一个参数可以对初始值进行改造 | combineByKey |
sortByKey([ascending], [numTasks]) | 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD | sortByKey |
mapValues | 只针对于value进行操作 | mapvalue |
join(otherDataset, [numTasks]) | 比如原始结果为a1(1,5),a2(1,9)那么进行join就变成了(1,(5,9)) | join |
cogroup(otherDataset, [numTasks]) | 其实比较类似于上面函数,只不过返回的不是元组而是迭代器 | cogroup |
1、partitionBy
需求:创建一个4个分区的RDD,对其重新分区
val rdd = sc.parallelize(Array((1,"aaa"),(2,"bbb"),(3,"ccc"),(4,"ddd")),4)
var rdd2 = rdd.partitionBy(new org.apache.spark.HashPartitioner(2))
2、groupByKey
创建一个pairRDD,将相同key对应值聚合到一个sequence中,并计算相同key对应值的相加结果。
val words = Array("one", "two", "two", "three", "three", "three")
val wordPairsRDD = sc.parallelize(words).map(word => (word, 1))
val group = wordPairsRDD .groupByKey()
group.collect()
输出结果为: Array((two,CompactBuffer(1, 1)), (one,CompactBuffer(1)), (three,CompactBuffer(1, 1, 1)))
val res2=group.map((k,v)=>(k,v.sum))
res2.collect()
输出结果:Array[(String, Int)]