spark RDD和RDD算子

什么是RDD?

RDD全称resilient distributed dataset(弹性分布式数据集)。他是一个弹性分布式数据集,是spark里面抽象的概念。代表的是一个不可变的,集合里面的元素可以分区的支持并行化的操作。

RDD产生的意义在于降低开发分布式应用程序的门槛和提高执行效率。它是一个可以容错的不可变集合,集合中的元素可以进行并行化地处理,Spark是围绕RDDs的概念展开的。RDD可以通过有两种创建的方式,一种是通过已经存在的驱动程序中的集合进行创建,另一种是通引用外部存储系统中的数据集进行创建,这里的外部系统可以是像HDFS或HBase这样的共享文件系统,也可以是任何支持hadoop InputFormat的数据。

在源码中,RDD是一个具备泛型的可序列化的抽象类。具备泛型意味着RDD内部存储的数据类型不定,大多数类型的数据都可以存储在RDD之中。RDD是一个抽象类则意味着RDD不能直接使用,我们使用的时候通常使用的是它的子类,如HadoopRDD,BlockRDD,JdbcRDD,MapPartitionsRDD,CheckpointRDD等。


RDD的五大特性:

1、RDD是由一系列的分区组成。

2、一个操作作用于每一个分区。

3、RDD之间存在各种依赖关系。

4、可选的特性,key-value型的RDD是通过hash进行分区。

5、RDD的每一个分区在计算时会选择最佳的计算位置。(数据本地化)


RDD的创建方式:

sc.parallelize

外部数据源


Spark中的算子:

1、RDD可以分为两类,transformations和actions。
2、Transformations 变换/转换算子:将一个RDD转换成另一个RDD,所有的Transformation都是lazy的,只有发生action是才会触发计算。
3、Action 行动算子:这类算子会触发 SparkContext 提交 作业。
####思考:spark官网说这样设置算子会使spark运行地更加的高效,请问这是为什么呢?
答:1)假设执行一个rdda.map().reduce()的操作,如果作为转换算子map()也触发计算,则肯定得将结果写出来,降低效率。
        2)由于lineage的关系,之后详细讲解。
4、当你对一个RDD进行转换时,只要触发action操作就可能会引起RDD的重算,RDD的重算机制使得当某个RDD数据丢失重算可以恢复该RDD。
5、你可以通过persist()或cache()方法将RDD保存在内存中,这样的话,在下次查询时可以更快地访问到RDD中的元素。spark也支持将RDD存储在磁盘上,也支持RDD的跨节点复制。


Transformations中的算子


map(func) 
返回一个新的分布式数据集,由每个原元素经过func函数处理后的新元素组成 
scala> var data = sc.parallelize(1 to 9,3)
//内容为 Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
scala> data.map(x=>x*2).collect()
//输出内容 Array[Int] = Array(2, 4, 6, 8, 10, 12, 14, 16, 18)

filter(func) 
返回一个新的数据集,由经过func函数处理后返回值为true的原元素组成 
scala> var data = sc.parallelize(1 to 9,3)
//内容为 Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
scala> data.filter(x=> x%2==0).collect()
//输出内容 Array[Int] = Array(2, 4, 6, 8)

flatMap(func) 
类似于map,但是每一个输入元素,会被映射为0个或多个输出元素,(因此,func函数的返回值是一个seq,而不是单一元素) 
scala> var data = sc.parallelize(1 to 4,1)
//输出内容为 Array[Int] = Array(1, 2, 3, 4)
scala> data.flatMap(x=> 1 to x).collect()
//输出内容为 Array[Int] = Array(1, 1, 2, 1, 2, 3, 1, 2, 3, 4)

mapPartitions(func) 
类似于map,对RDD的每个分区起作用,在类型为T的RDD上运行时,func的函数类型必须是Iterator[T]=>Iterator[U]
//首先创建三个分区
scala> var data = sc.parallelize(1 to 9,3)
//输出为 Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
//查看分区的个数
scala> data.partitions.size
//输出为 Int = 3
//使用mapPartitions
scala> var result = data.mapPartitions{ x=> {
var res = List[Int]()
var i = 0
while(x.hasNext){
i+=x.next()
}
res.::(i).iterator
 }}
//:: 该方法被称为cons,意为构造,向队列的头部追加数据,创造新的列表。用法为 x::list,其中x为加入到头部的元素,无论x是列表与否,它都只将成为新生成列表的第一个元素,也就是说新生成的列表长度为list的长度+1(btw, x::list等价于list.::(x))
scala> result.collect
//输出为 Array[Int] = Array(6, 15, 24)

mapPartitionsWithIndex(func) 和mapPartitions类似,但func带有一个整数参数表上分区的索引值,在类型为T的RDD上运行时,func的函数参数类型必须是(int,Iterator[T])=>Iterator[U] 
//首先创建三个分区
scala> var data = sc.parallelize(1 to 9,3)
//输出为 Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
//查看分区的个数
scala> data.partitions.size
//输出为 Int = 3
scala> var result = data.mapPartitionsWithIndex{
(x,iter) => {
var result = List[String]()
var i = 0
while(iter.hasNext){
i += iter.next()
}
result.::( x + "|" +i).iterator
}}
result.collect
//输出结果为 Array[String] = Array(0|6, 1|15, 2|24)

sample(withReplacement,fraction,seed) 
根据给定的随机种子seed,随机抽样出数量为fraction的数据 (第一个参数withReplacement这个值如果是true时,采用PoissonSampler取样器(Poisson分布),否则使用BernoulliSampler的取样器.;第二个参数fraction 一个大于0,小于或等于1的小数值,用于控制要读取的数据所占整个数据集的概率;第三个参数seed 表示随机的种子)
//创建数据
var data = sc.parallelize(1 to 1000,1)
//采用固定的种子seed随机
data.sample(true,0.005,0).collect
//输出为 Array[Int] = Array(192, 435, 459, 647, 936)
data.sample(false,0.005,0).collect
//输出为 Array[Int] = Array(192, 795, 826)
//采用随机种子
data.sample(false,0.005,scala.util.Random.nextInt(1000)).collect
//输出为 Array[Int] = Array(136, 158)


union(otherDataSet) 
返回一个新的数据集,由原数据集合参数联合而成 (不会去重)
//创建第一个数据集
scala> var data1 = sc.parallelize(1 to 5,1)
//创建第二个数据集
scala> var data2 = sc.parallelize(3 to 7,1)
//取并集
scala> data1.union(data2).collect
//输出为 Array[Int] = Array(1, 2, 3, 4, 5, 3, 4, 5, 6, 7)

intersection(otherDataset) 
求两个RDD的交集 
//创建第一个数据集
scala> var data1 = sc.parallelize(1 to 5,1)
//创建第二个数据集
scala> var data2 = sc.parallelize(3 to 7,1)
//取交集
scala> data1.intersection(data2).collect
//输出为 Array[Int] = Array(4, 3, 5)


distinct([numtasks]) 
返回一个包含源数据集中所有不重复元素的新数据集(去重)
//创建数据集
scala> var data = sc.parallelize(List(1,1,1,2,2,3,4),1)
//执行去重
scala> data.distinct.collect
//输出为 Array[Int] = Array(4, 1, 3, 2)
//如果是键值对的数据,kv都相同,才算是相同的元素
scala> var data = sc.parallelize(List(("A",1),("A",1),("A",2),("B",1)))
//执行去重
scala> data.distinct.collect
//输出为 Array[(String, Int)] = Array((A,1), (B,1), (A,2))

groupByKey([numtasks]) 
在一个由(K,v)对组成的数据集上调用,返回一个(K,Seq[V])对组成的数据集。
如果想要对key进行聚合的话,使用reduceByKey或者combineByKey会有更好的性能 
默认情况下,输出结果的并行度依赖于父RDD的分区数目
//创建数据集
scala> var data = sc.parallelize(List(("A",1),("A",1),("A",2),("B",1)))
//分组输出
scala> data.groupByKey.collect
//输出为 Array[(String, Iterable[Int])] = Array((B,CompactBuffer(1)), (A,CompactBuffer(1, 1, 2)))

reduceByKey(func,[numTasks]) 
在一个(K,V)对的数据集上使用,返回一个(K,V)对的数据集,key相同的值,都被使用指定的reduce函数聚合到一起,reduce任务的个数是可以通过第二个可选参数来配置的 
//创建数据集
scala> var data = sc.parallelize(List(("A",1),("A",1),("A",2),("B",1)))
scala> data.reduceByKey((x,y) => x+y).collect
//输出为 Array[(String, Int)] = Array((B,1), (A,4))


aggregateByKey(zeroValue)(seqOp, combOp, [numTasks])
在(k,v)对的数据集上调用时,返回一个数据集(k,u)对,其中每个键的值使用给定的组合函数和一个中立的“0”值聚合。
允许与输入值类型不同的聚合值类型,同时避免不必要的分配。像groupbykey,数量减少的任务是通过配置一个可选参数。
scala> var data = sc.parallelize(List((1,1),(1,2),(1,3),(2,4)),2)
scala> def sum(a:Int,b:Int):Int = { a+b }
scala> data.aggregateByKey(0)(sum,sum).collect
res42: Array[(Int, Int)] = Array((2,4), (1,6))
scala> def max(a:Int,b:Int):Int = { math.max(a,b) }
scala> data.aggregateByKey(0)(max,sum).collect
res44: Array[(Int, Int)] = Array((2,4), (1,5))

sortByKey([ascending],[numTasks]) 
在类型为(K,V)的数据集上调用,返回以K为键进行排序的(K,V)对数据集,升序或者降序有boolean型的ascending参数决定
//创建数据集
scala> var data = sc.parallelize(List(("A",2),("B",2),("A",1),("B",1),("C",1)))
data: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[30] at parallelize at <console>:27
//对数据集按照key进行默认排序
scala> data.sortByKey().collect
res23: Array[(String, Int)] = Array((A,2), (A,1), (B,2), (B,1), (C,1))
//升序排序
scala> data.sortByKey(true).collect
res24: Array[(String, Int)] = Array((A,2), (A,1), (B,2), (B,1), (C,1))
//降序排序
scala> data.sortByKey(false).collect
res25: Array[(String, Int)] = Array((C,1), (B,2), (B,1), (A,2), (A,1))

join(otherDataset,[numTasks]) 
在类型为(K,V)和(K,W)类型的数据集上调用,返回一个(K,(V,W))对,每个key中的所有元素都在一起的数据集 ,外连接是通过leftouterjoin,rightouterjoin支持,和fullouterjoin。
//创建第一个数据集
scala> var data1 = sc.parallelize(List(("A",1),("A",2),("C",3)))
//创建第二个数据集
scala> var data2 = sc.parallelize(List(("A",4)))
//创建第三个数据集
scala> var data3 = sc.parallelize(List(("A",4),("A",5)))
data1.join(data2).collect
//输出为 Array[(String, (Int, Int))] = Array((A,(1,4)), (A,(2,4)))
data1.join(data3).collect
//输出为 Array[(String, (Int, Int))] = Array((A,(1,4)), (A,(1,5)), (A,(2,4)), (A,(2,5)))

cogroup(otherDataset,[numTasks]) 
在类型为(K,V)和(K,W)类型的数据集上调用,返回一个数据集,为(K,Iterable[V],Iterable[W]) 元组,这种操作也被称为分组。
//创建第一个数据集
scala> var data1 = sc.parallelize(List(("A",1),("A",2),("C",3)))
//创建第二个数据集
scala> var data2 = sc.parallelize(List(("A",4)))
//创建第三个数据集
scala> var data3 = sc.parallelize(List(("A",4),("A",5)))
scala> data1.cogroup(data2).collect
//Array[(String, (Iterable[Int], Iterable[Int]))] = Array((A,(CompactBuffer(1, 2),CompactBuffer(4))), (C,(CompactBuffer(3),CompactBuffer())))
scala> data1.cogroup(data3).collect
//Array[(String, (Iterable[Int], Iterable[Int]))] = Array((A,(CompactBuffer(1, 2),CompactBuffer(4, 5))), (C,(CompactBuffer(3),CompactBuffer())))

cartesian(otherDataset) 
笛卡尔积,但在数据集T和U上调用时,返回一个(T,U)对的数据集,所有元素交互进行笛卡尔积 
//创建第一个数据集
scala> var a = sc.parallelize(List(1,2))
//创建第二个数据集
scala> var b = sc.parallelize(List("A","B"))
//计算笛卡尔积
scala> a.cartesian(b).collect
//输出结果 res2: Array[(Int, String)] = Array((1,A), (1,B), (2,A), (2,B))


pipe(command,[envVars]) 
通过管道的方式对RDD的每个分区使用shell命令进行操作,返回对应的结果 。分区的元素将会被当做输入,脚本的输出则被当做返回的RDD值。
//创建数据集
scala> var data = sc.parallelize(1 to 9,3)
//测试脚本
scala> data.pipe("head -n 1").collect
res26: Array[String] = Array(1, 4, 7)
scala> data.pipe("tail -n 1").collect
res27: Array[String] = Array(3, 6, 9)
scala> data.pipe("tail -n 2").collect
res28: Array[String] = Array(2, 3, 5, 6, 8, 9)


coalesce(numPartitions) 
对RDD中的分区减少指定的数目,通常在过滤完一个大的数据集之后进行此操作 ,用于合并小文件(第一个参数是分区的数量,第二个参数是是否进行shuffle)
//创建数据集
scala> var data = sc.parallelize(1 to 9,3)
data: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[6] at parallelize at <console>:27
//查看分区的大小
scala> data.partitions.size
res3: Int = 3
//不使用shuffle重新分区
scala> var result = data.coalesce(2,false)
result: org.apache.spark.rdd.RDD[Int] = CoalescedRDD[19] at coalesce at <console>:29
scala> result.partitions.length
res12: Int = 2
scala> result.toDebugString
res13: String = 
(2) CoalescedRDD[19] at coalesce at <console>:29 []
 |  ParallelCollectionRDD[9] at parallelize at <console>:27 []
//使用shuffle重新分区
scala> var result = data.coalesce(2,true)
result: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[23] at coalesce at <console>:29
scala> result.partitions.length
res14: Int = 2
scala> result.toDebugString
res15: String = 
(2) MapPartitionsRDD[23] at coalesce at <console>:29 []
 |  CoalescedRDD[22] at coalesce at <console>:29 []
 |  ShuffledRDD[21] at coalesce at <console>:29 []
 +-(3) MapPartitionsRDD[20] at coalesce at <console>:29 []
    |  ParallelCollectionRDD[9] at parallelize at <console>:27 []

repartition(numpartitions) 
重新洗牌的RDD数据随机到创建更多或更少的分区中并且使数据再平衡。
用于增加或减少RDD的并行度(分区数)和解决数据倾斜的。是一个默认使用shuffle的coalesce,如果是用于减少分区数量,请使用coalesce,避免使用shuffle。
//创建数据集
scala> var data = sc.parallelize(1 to 9,3)
data: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[6] at parallelize at <console>:27
//查看分区的大小
scala> data.partitions.size
res3: Int = 3
scala> var result = data.repartition(2)
result: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[27] at repartition at <console>:29
scala> result.partitions.length
res16: Int = 2
scala> result.toDebugString
res17: String = 
(2) MapPartitionsRDD[27] at repartition at <console>:29 []
 |  CoalescedRDD[26] at repartition at <console>:29 []
 |  ShuffledRDD[25] at repartition at <console>:29 []
 +-(3) MapPartitionsRDD[24] at repartition at <console>:29 []
    |  ParallelCollectionRDD[9] at parallelize at <console>:27 []

repartitionAndSortWithinPartitions(partitioner)
这个方法是在分区中按照key进行排序,这种方式比先分区再sort更高效,因为相当于在shuffle阶段就进行排序。
scala> var data = sc.parallelize(List((1,2),(1,1),(2,3),(2,1),(1,4),(3,5)),2)
data: org.apache.spark.rdd.RDD[(Int, Int)] = ParallelCollectionRDD[60] at parallelize at <console>:27
scala> data.repartitionAndSortWithinPartitions(new org.apache.spark.HashPartitioner(2)).collect
res52: Array[(Int, Int)] = Array((2,3), (2,1), (1,2), (1,1), (1,4), (3,5))
scala> data.repartitionAndSortWithinPartitions(new org.apache.spark.HashPartitioner(1)).collect
res53: Array[(Int, Int)] = Array((1,2), (1,1), (1,4), (2,3), (2,1), (3,5))
scala> data.repartitionAndSortWithinPartitions(new org.apache.spark.HashPartitioner(3)).collect
res54: Array[(Int, Int)] = Array((3,5), (1,2), (1,1), (1,4), (2,3), (2,1))


Action中的算子:


reduce(func) 
通过函数func聚集数据集中的所有元素,这个函数必须是关联性的,确保可以被正确的并发执行 
//创建数据集
scala> var data = sc.parallelize(1 to 3,1)
scala> data.collect
res6: Array[Int] = Array(1, 2, 3)
//collect计算
scala> data.reduce((x,y)=>x+y)
res5: Int = 6

collect() 
在driver的程序中,以数组的形式,返回数据集的所有元素,这通常会在使用filter或者其它操作后,返回一个足够小的数据子集再使用 
//创建数据集
scala> var data = sc.parallelize(1 to 3,1)
scala> data.collect
res6: Array[Int] = Array(1, 2, 3)

count() 
返回数据集的元素个数 
//创建数据集
scala> var data = sc.parallelize(1 to 3,1)
//统计个数
scala> data.count
res7: Long = 3
scala> var data = sc.parallelize(List(("A",1),("B",1)))
scala> data.count
res8: Long = 2


first() 
返回数据集的第一个元素(类似于take(1)) 
//创建数据集
scala> var data = sc.parallelize(List(("A",1),("B",1)))
//获取第一条元素
scala> data.first
res9: (String, Int) = (A,1)


take(n) 
返回一个数组,由数据集的前n个元素组成。注意此操作目前并非并行执行的,而是driver程序所在机器 
//创建数据集
scala> var data = sc.parallelize(List(("A",1),("B",1),("B",2),("C",1)))
scala> data.take(1)
res10: Array[(String, Int)] = Array((A,1))
//如果n大于总数,则会返回所有的数据
scala> data.take(8)
res12:  Array[(String, Int)] = Array((A,1), (B,1), (B,2), (C,1))
//如果n小于等于0,会返回空数组
scala> data.take(-1)
res13: Array[(String, Int)] = Array()
scala> data.take(0)
res14: Array[(String, Int)] = Array()


takeSample(withReplacement,num,seed) 
返回一个数组,在数据集中随机采样num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定的随机数生成器种子 
这个方法与sample还是有一些不同的,主要表现在:
返回具体个数的样本(第二个参数指定)
直接返回array而不是RDD
内部会将返回结果随机打散
//创建数据集
scala> var data = sc.parallelize(List(1,3,5,7))
data: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at <console>:21
//随机2个数据
scala> data.takeSample(true,2,1)
res0: Array[Int] = Array(7, 1)
//随机4个数据,注意随机的数据可能是重复的
scala> data.takeSample(true,4,1)
res1: Array[Int] = Array(7, 7, 3, 7)
//第一个参数是是否重复
scala> data.takeSample(false,4,1)
res2: Array[Int] = Array(3, 5, 7, 1)
scala> data.takeSample(false,5,2)
res3: Array[Int] = Array(3, 5, 7, 1)

takeOrderd(n,[ordering]) 
排序后的limit(n) 
//创建数据集
scala> var data = sc.parallelize(List("b","a","e","f","c"))
data: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[3] at parallelize at <console>:21
//返回排序数据
scala> data.takeOrdered(3)
res4: Array[String] = Array(a, b, c)

saveAsTextFile(path) 
将数据集的元素,以textfile的形式保存到本地文件系统hdfs或者任何其他hadoop支持的文件系统,spark将会调用每个元素的toString方法,并将它转换为文件中的一行文本 
//创建数据集
scala> var data = sc.parallelize(List("b","a","e","f","c"))
data: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[3] at parallelize at <console>:21
//保存为test_data_save文件
scala> data.saveAsTextFile("test_data_save")
scala> data.saveAsTextFile("test_data_save2",classOf[GzipCodec])
<console>:24: error: not found: type GzipCodec
              data.saveAsTextFile("test_data_save2",classOf[GzipCodec])
                                                           ^
//引入必要的class
scala> import org.apache.hadoop.io.compress.GzipCodec
import org.apache.hadoop.io.compress.GzipCodec
//保存为压缩文件
scala> data.saveAsTextFile("test_data_save2",classOf[GzipCodec])


saveAsSequenceFile(path) 
将数据集的元素,以sequencefile的格式保存到指定的目录下,本地系统,hdfs或者任何其他hadoop支持的文件系统,RDD的元素必须由key-value对组成。并都实现了hadoop的writable接口或隐式可以转换为writable 

saveAsObjectFile(path) 
使用java的序列化方法保存到本地文件,可以被sparkContext.objectFile()加载 

countByKey() 
对(K,V)类型的RDD有效,返回一个(K,Int)对的map,表示每一个可以对应的元素个数 
//创建数据集
scala> var data = sc.parallelize(List(("A",1),("A",2),("B",1)))
data: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[7] at parallelize at <console>:22
//统计个数
scala> data.countByKey
res9: scala.collection.Map[String,Long] = Map(B -> 1, A -> 2)


foreache(func) 
在数据集的每一个元素上,运行函数func,t通常用于更新一个累加器变量,或者和外部存储系统做交互
// 创建数据集
scala> var data = sc.parallelize(List("b","a","e","f","c"))
data: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[10] at parallelize at <console>:22
// 遍历
scala> data.foreach(x=>println(x+" hello"))
b hello
a hello
e hello
f hello
c hello

参考博客:
http://www.cnblogs.com/xing901022/p/5947706.html
https://www.cnblogs.com/xing901022/p/5944297.html


【来自@若泽大数据】

猜你喜欢

转载自blog.csdn.net/weixin_39182877/article/details/79975251