Spark从入门到精通第六课:RDD中的常用算子全面剖析

1、概述 

分类:
    Transformations 和Actions  以及 持久化算子

Transformations :
    将一个RDD转换成另一个RDD
    所有的Transformation都是lazy的,只有发生action是才会触发计算

Action:
    这类算子会触发 SparkContext提交作业
    一个action算子就是一个job(作业)

问题:
    spark官网说这样设置算子会使spark运行地更加的高效,请问这是为什么呢?

    答:假设执行一个rdda.map().reduce()的操作,如果作为转换算子map()也触发计算,则肯定得将    
        结果写出来,降低效率。第二则是由于lineage的关系。

持久化算子:
    略
    参考:https://blog.csdn.net/pengzonglu7292/article/details/79485271

2、Transformations中的算子

1、map && flatMap
    scala> sc.makeRDD(List("hello spark","hello hive")).flatMap(_.split("     
        ")).map((_,1)).reduceByKey(_+_).collect
    res1: Array[(String, Int)] = Array((hive,1), (spark,1), (hello,2))
    总结:
        map     一进一出
        flatMap 一进多出
        flatMap会将String看成是一个字符数组,不会将Array[String]看成字符数组


2、mapValues &&    flatMapValues
    对k-v型rdd的value进行map和flatMap
操作:
    scala>  sc.makeRDD(List("hello" -> 1)).mapValues(_+1).collect
    res2: Array[(String, Int)] = Array((hello,2)) 

    scala>  sc.makeRDD(Array(2 -> "hive on spark")).flatMapValues(_.split(" ")).collect
    res3: Array[(Int, String)] = Array((2,hive), (2,on), (2,spark))
         

3、filter && distinct
    filter:对rdd数据过滤
操作:
    scala> sc.parallelize(1 to 9).filter(_%2==0).collect
    res4: Array[Int] = Array(2, 4, 6, 8)

    distinct:会对数据进行去重
    scala>  sc.makeRDD(List(2,3,2,4)).distinct.collect
    res5: Array[Int] = Array(4, 2, 3)


4、sample && glom
    sample:对rdd数据进行抽样
    第一个参数:是否放回,true放回,false不放回
    第二个参数:抽样比例,大概比例,并非精准按比例抽样
    第三个参数:种子数,可忽略
操作:
    scala> sc.parallelize(1 to 100).sample(true,0.1).collect
    res6: Array[Int] = Array(9, 22, 30, 37, 43, 51, 74, 83)  
    
    glom: 将一个分区所有数据装入Array中,这样rdd中每一个分区就只包含一个数组
操作:
    scala> sc.makeRDD(1 to 10,3).glom().collect()
    res7: Array[Array[Int]] = Array(Array(1, 2, 3), Array(4, 5, 6), Array(7, 8, 9, 10))


5、coalesce && repartition  
    coalesce默认不shuffle,开启shuffle第2个参数设为true
    repartition默认shuffle,无第2参数,故无法关闭shuffle
    注:
        由于增大分区必需开启shuffle,故用repartition增大分区,coalesce减小分区        
操作:
    scala>  sc.makeRDD(1 to 10,2).coalesce(1).partitions.size
    res8: Int = 1
    scala>  sc.makeRDD(1 to 10,2).repartition(3).partitions.size
    res9: Int = 3   

问题:
    shuffle作为一个昂贵的操作,我们一定要尽可能的规避shuffle?
答:
    一般情况下尽可能规避shuffle没有错,但在分区数据倾斜的情况下,利用重分区算子开启
    shuffle,就能起到平均分区数据的作用。
    

6、intersection && subtract && union
    intersection:返回两个RDD的交集,并且去重。分数区等于max{两个父rdd分区数}
操作:
    scala> sc.makeRDD(Array(1,1,3),3).intersection(sc.makeRDD(Array(1,1,4),2)).collect
    res10: Array[Int] = Array(1)

    subtract:返回两个rdd的差集,不去重。谁调用分区数和谁一致
操作:
    scala> sc.makeRDD(Array(1,2,2),2).subtract(sc.makeRDD(Array(1,3,3),3)).collect
    res11: Array[Int] = Array(2, 2)
    scala> sc.makeRDD(Array(1,3,3),2).subtract(sc.makeRDD(Array(1,2,2),3)).collect
    res12: Array[Int] = Array(3, 3)

    union:返回两个rdd的并集,不去重。合并后分区数等于两父rdd之和。
操作:
    scala> sc.makeRDD(1 to 4,3).union(sc.makeRDD(5 to 10,2)).collect
    res13: Array[Int] = Array(5, 6, 7, 8, 9, 10, 1, 2, 3, 4)

 
7、mapPartitions  &&  mapPartitionsWithIndex
    mapPartitions:和map一样,只不过操作的数据是一个分区。在操作数据库时应用广泛
操作:
    scala> val a=sc.makeRDD(1 to 5,2).mapPartitions(
     | x =>{                //x代表一个分区的所有数据
     | var list=List()
     | var i=0
     | while(x.hasNext){
     | i+=x.next()        //将每个分区数据相加
     | }
     | (i::list).iterator
     | }
     | ).collect
    res14: Array[Int] = Array(3, 12)
    
    mapPartitionsWithIndex:与mapPartitions相比,该算子有两个参数,第一个参数为分区索引
操作:
    scala>  sc.makeRDD(1 to 5,2).mapPartitionsWithIndex(
     | (index,x) => {
     | var list=List()
     | var i=0
     | while(x.hasNext){
     | i+=x.next()
     | }
     | ((index,i)::list).iterator
     | }
     | ).collect
res15: Array[(Int, Int)] = Array((0,3), (1,12))


8、zip  && zipPartitions && zipWithIndex &&zipWithUniqueId
    zip:两个rdd合并成一个k-v型的rdd,要求两个rdd分区数和对应分区内元素个数一致,否则异常
操作:
    scala>  sc.makeRDD(1 to 3,2).zip(sc.makeRDD(4 to 6,2)).collect
    res16: Array[(Int, Int)] = Array((1,4), (2,5), (3,6))

    zipPartitions:将两个rdd合并成一个k-v形式rdd,要求两个rdd分区数一致,否则异常
操作:
    略

    zipWithIndex:将rdd中的元素和索引号形成键值对,返回一个新的rdd
操作:
    scala>  sc.makeRDD(1 to 3,2).zipWithIndex().collect    #和分区无关
    res17: Array[(Int, Long)] = Array((1,0), (2,1), (3,2))

    zipWithUniqueId:将rdd中的元素与唯一id形成键值对,返回一个新的rdd
    唯一id生成算法:
        分区内第一个元素:唯一id为分区号
        分区内非首个元素:唯一id为前一个元素id + rdd总共的分区数
操作:
    scala>  sc.makeRDD(1 to 5,2).zipWithUniqueId().collect
    res18: Array[(Int, Long)] = Array((1,0), (2,2), (3,1), (4,3), (5,5))

    
9、randomSplit
    将一个RDD切分成多个RDD,返回一个RDD数组
操作:
    scala>val sr = sc.makeRDD(1 to 10).randomSplit(Array(1.0,2.0,3.0))
    scala> sr(0).collect
    res19: Array[Int] = Array()                                                      
    scala> sr(1).collect
    res20: Array[Int] = Array(3, 6, 10)
    scala> sr(2).collect
    res21: Array[Int] = Array(1, 2, 4, 5, 7, 8, 9)
解释:
    该算子的第一个函数为权重(要求double类型),权重越高的RDD,划分到数据的几率越大。
    第二个参数为种子,可以忽略。


   

10、foldByKey && groupByKey   && groupBy 
    foldByKey:将rdd中的键值对根据key将value折叠
操作:
    scala>  sc.makeRDD(Array(("hello",1),("hello",2),("spark",2),("spark",0))).foldByKey(0)(_+_).collect    
    res22: Array[(String, Int)] = Array((spark,2), (hello,3))    //key相加后再加0
    scala>  sc.makeRDD(Array(("hello",1),("hello",2),("spark",2),("spark",0))).foldByKey(1)(_+_).collect     
    res23: Array[(String, Int)] = Array((spark,4), (hello,5))    //key相加后再加1
    scala>  sc.makeRDD(Array(("hello",1),("hello",2),("spark",2),    ("spark",0))).foldByKey(0)(_*_).collect    
    res24: Array[(String, Int)] = Array((spark,0), (hello,0))    //key相乘后再乘0
    scala>  sc.makeRDD(Array(("hello",1),("hello",2),("spark",2),("spark",0))).foldByKey(1)(_*_).collect     
    res25: Array[(String, Int)] = Array((spark,0), (hello,2))    //key相乘后再加1

    groupByKey:按key分组,value放入集合中,唯一参数可以是分区数或分区函数或不传
操作:
    scala>  sc.makeRDD(Array(("hello",1),("hello",2),("spark"
        ,2),("spark",0))).groupByKey().collect
    res26: Array[(String, Iterable[Int])] = Array((spark,CompactBuffer(2, 0)), 
    (hello,CompactBuffer(1, 2)))

    groupBy:按分区函数分组,key-value放入集合中
操作:
    scala> sc.makeRDD(Array(("hello",1),("hello",2),("spark",2),("spark",0))).groupBy(x     
        => x._1).collect
    res3: Array[(String, Iterable[(String, Int)])] = 
        Array((spark,CompactBuffer((spark,2), (spark,0))), (hello,CompactBuffer((hello,1), (hello,2))))

11、reduceByKey  &&  reduceByKeyLocally
    reduceByKey:按key对value进行运算,
操作:
    scala>  sc.makeRDD(Array(("hello",1),("hello",2),("spark",2),("spark",0))).reduceByKey((_+_),2).collect    //指定分区数
    res27: Array[(String, Int)] = Array((hello,3), (spark,2))
    scala>  sc.makeRDD(Array(("hello",1),("hello",2),("spark",2),("spark",0))).reduceByKey(_+_).collect    
    res28: Array[(String, Int)] = Array((spark,2), (hello,3))
    scala>  sc.makeRDD(Array(("hello",1),("hello",2),("spark",2),("spark",0))).reduceByKey(new org.apache.spark.HashPartitioner(2),(_+_)).collect
    res29: Array[(String, Int)] = Array((hello,3), (spark,2))    //指定分区函数

    reduceByKeyLocally:按key对value进行运算,返回一个map集合
操作:
    scala>  sc.makeRDD(Array(("hello",1),("hello",2),("spark",2),
        ("spark",0))).reduceByKeyLocally(_+_)
    res30: scala.collection.Map[String,Int] = Map(spark -> 2, hello -> 3)


12、partitionBy  && cogroup
    partitionBy:根据传入的分区函数对key-value行的rdd重新进行分区
操作:
    scala>  sc.makeRDD(Array(("hello",1),("spark",1),("hive",2))).partitionBy(new     org.apache.spark.HashPartitioner(3)).partitions.size
    res31: Int = 3

    cogroup:相当于全外连接,参数可以有多个
操作:
    scala>  sc.makeRDD(Array((1,"a"),(2,"b"))).cogroup(sc.makeRDD(Array((1,"aa"),(3,"c")))).collect
    res32: Array[(Int, (Iterable[String], Iterable[String]))] = Array((1,(CompactBuffer(a),CompactBuffer(aa))), (2,(CompactBuffer(b),CompactBuffer())), (3,(CompactBuffer(),CompactBuffer(c))))

    可以2个以上的rdd join

13、join && leftOuterJoin && rightOuterJoin && fullOuterJoin
    scala> sc.makeRDD(Array((1,"zhang3"),(3,"li4"))).join((sc.makeRDD(Array((1,"19"),(2,20))))).collect
    res33: Array[(Int, (String, Any))] = Array((1,(zhang3,19)))

    scala> sc.makeRDD(Array((1,"zhang3"),(3,"li4"))).leftOuterJoin((sc.makeRDD(Array((1,"19"),(2,20))))).collect
    res34: Array[(Int, (String, Option[Any]))] = Array((1,(zhang3,Some(19))), (3,(li4,None)))

    scala> sc.makeRDD(Array((1,"zhang3"),(3,"li4"))).rightOuterJoin((sc.makeRDD(Array((1,"19"),(2,20))))).collect
    res35: Array[(Int, (Option[String], Any))] = Array((1,(Some(zhang3),19)), (2,(None,20)))

    scala> sc.makeRDD(Array((1,"zhang3"),(3,"li4"))).fullOuterJoin((sc.makeRDD(Array((1,"19"),(2,20))))).collect
    res36: Array[(Int, (Option[String], Option[Any]))] = Array((1,(Some(zhang3),Some(19))), (2,(None,Some(20))), (3,(Some(li4),None)))


14、sortBy   &&  sortByKey
sortBy:
    参数1:排序函数   参数2:排序方式,默认true升序  参数3:排序后的分区数
    只有第一个参数是必须要传入的,底层调用了sortByKey
操作:
    scala> sc.makeRDD(Array(2,3,1,4,5)).sortBy(x=> x).collect
    res7: Array[Int] = Array(1, 2, 3, 4, 5)
    scala> sc.makeRDD(Array(2,3,1,4,5)).sortBy(x=> x,false).collect
    res8: Array[Int] = Array(5, 4, 3, 2, 1)
    scala> sc.makeRDD(Array(2,3,1,4,5)).sortBy(x=> x,false,3).collect
    res9: Array[Int] = Array(5, 4, 3, 2, 1)

sortByKey:
    按k-v型rdd的key排序
    参数1:排序方式,默认true升序   参数2:排序后的分区数
操作:
    scala> sc.makeRDD(Array((2,"lily"),(1,"lucy"),(3,"cindy"))).sortByKey().collect
    res10: Array[(Int, String)] = Array((1,lucy), (2,lily), (3,cindy))

    scala> sc.makeRDD(Array((2,"lily"),(1,"lucy"),(3,"cindy"))).sortByKey(false,2).collect
    res11: Array[(Int, String)] = Array((3,cindy), (2,lily), (1,lucy))


sortBy &&  sortByKey:
    虽然他们是transformations算子,但他们仍然会触发作业。
    
 

3、Actions算子

1、first  &&  take  && top  &&  takeOrdered  
first:返回一个元素
    scala> sc.makeRDD(1 to 10).first()
    res37: Int = 1

take:返回前n个元素
    scala> sc.makeRDD(1 to 10).take(2)
    res38: Array[Int] = Array(1, 2)

top:按照默认顺序(降序)取前n个元素
    scala> sc.makeRDD(1 to 10).top(2)
    res39: Array[Int] = Array(10, 9)

takeOrdered:按照升序取前n个元素
    scala> sc.makeRDD(1 to 10).takeOrdered(2)
    res40: Array[Int] = Array(1, 2)


2、collect  &&  count  && reduce
collect:将一个rdd转成数组,返回到driver端
    scala> sc.makeRDD(List(1,2,3,4)).collect
    res41: Array[Int] = Array(1, 2, 3, 4)
count:返回rdd中元素的数量
    scala> sc.makeRDD(List(1,2,3,4)).count
    res42: Long = 4
reduce:对rdd中的元素进行二元计算
    scala> sc.makeRDD(1 to 10).reduce(_+_)
    res43: Int = 55 



3、foreach &&  lookup  &&  foreachPartition
foreach:打印rdd中的元素
    scala> sc.makeRDD(1 to 10).foreach(print)
    67891012345
lookup:给出key值,返回value
    scala> sc.makeRDD(Array((1,"zhang3"),(3,"li4"))).lookup(1)
    res44: Seq[String] = WrappedArray(zhang3)
foreachPartition:
    

4、countByKey   
countByKey:返回k-v中每个key出现的次数
    scala> sc.makeRDD(Array(("A",0),("A",2),("B",1),("B",2),("B",3))).countByKey
    res45: scala.collection.Map[String,Long] = Map(A -> 2, B -> 3)

5、saveAsTextFile  &&  saveAsObjectFile  &&  saveAsHadoopFile  &&  saveAsHadoopDataset
    这几个算子都可以指定特定的对应的codec
saveAsTextFile:
    将rdd数据保存在文件系统中,一个分区对应一个文件
操作:
    scala> sc.parallelize(1 to 10,2).saveAsTextFile("hdfs://Linux001/data/")    
    [root@Linux001 ~]# hdfs dfs -ls /data
    Found 3 items
    -rw-r--r--   1 root supergroup          0 2019-05-25 11:21 /data/a.txt/_SUCCESS
    -rw-r--r--   1 root supergroup          4 2019-05-25 11:21 /data/a.txt/part-00000
    -rw-r--r--   1 root supergroup          6 2019-05-25 11:21 /data/a.txt/part-00001
    [root@Linux001 ~]# hdfs dfs -cat /data/part-00000
    1
    2
    [root@Linux001 ~]# hdfs dfs -cat /data/part-00001
    3
    4
    5

saveAsObjectFile:将rdd数据保存在文件系统中,一个分区对应一个文件
操作:
    和saveAsTextFile一样,略





 

 

 

 

你可能感兴趣的:(Spark,Spark技术详解,RDD,Spark)