Spark-core的RDD算子总结

Spark核心编程
RDD:
RDD的概念: RDD(Resilient Distributed Dataset)叫做 弹性 分布式 数据集,是 Spark 中最基本的数据处理模型。
                       代码中是一个抽象类,它代表一个弹性的、 不可变、可分区、里面的元素可并行 计算的集合。
    ➢ 弹性
         ⚫ 存储的弹性:内存与磁盘的自动切换;
         ⚫ 容错的弹性:数据丢失可以自动恢复;
         ⚫ 计算的弹性:计算出错重试机制;
         ⚫ 分片的弹性:可根据需要重新分片。
    ➢ 分布式:数据存储在大数据集群不同节点上
    ➢ 数据集:RDD 封装了计算逻辑,并不保存数据 ➢ 数据抽象:RDD 是一个抽象类,需要子类具体实现
    ➢ 不可变:RDD 封装了计算逻辑,是不可以改变的,想要改变,只能产生新的 RDD,在 新的 RDD 里面封装计算
                      逻辑     
    ➢ 可分区、并行计算
ps:其实所谓的RDD就是创建一个RDD后使用了该RDD的转换算子后,就变成了一个新的RDD。(类似于一个嵌套的样子)实例如下:
RDD 的创建
    1)从集合(内存)中创建RDD,有两个方法(parallelize和makeRDD)示例如下:
           
     val rdd1 = sparkContext.parallelize( List(1,2,3,4) )
     val rdd2 = sparkContext.makeRDD( List(1,2,3,4) )
  ps:makeRDD方法其底层就是用的parallelize方法,只不过是为了编程者的方便!
    
    2)从外部存储(文件)创建RDD, 由外部存储系统的数据集创建 RDD 包括:本地的文件系统,所有 Hadoop 支持的数据集, 比如 HDFS、HBase 等。
            
    val fileRDD: RDD[String] = sparkContext.textFile("input")
    3) 从其他 RDD 创建,主要是通过一个 RDD 运算完后,再产生新的 RDD。(如RDD的ps一般,使用最多)
    4) 直接创建 RDD(new)
RDD 并行度与分区:
     默认情况下,Spark 可以将一个作业切分多个任务后,发送给 Executor 节点并行计算,而能 够并行计算的任务数量我们称之为并行度。这个数量可以在构建 RDD 时指定。记住,这里 的并行执行的任务数量,并不是指的切分任务的数量,不要混淆了。
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("spark") 
    val sparkContext = new SparkContext(sparkConf) 
    val dataRDD: RDD[Int] = sparkContext.makeRDD( List(1,2,3,4), 4
    val fileRDD: RDD[String] = sparkContext.textFile( "input", 2)                                     fileRDD.collect().foreach(println) sparkContext.stop()
RDD 转换算子:
    RDD 根据数据处理方式的不同将算子整体上分为 Value 类型、双 Value 类型和 Key-Value 类型。
  •     Value类型就是一个个数据。例如:List(1,2,3,4,5)
  •     双Value类型就是有两个数据集的关联操作,就叫双value。例如:两个List,做交并补。
  •     Key-Value类型就是两个数据为一个个体,然后有多组key-value。例如List(List(1,“hh”),List(2,“xixi”))。
  • Value 类型
        1)map
         ➢ 函数说明: 将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。
    val dataRDD1: RDD[Int] = dataRDD.map( 
        num => {
                num * 2 
                } 
     )
        2)mapPatitions
         ➢ 函数说明: 将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处 理,哪怕是过滤数据。(比map多一个分区的概念)
    val dataRDD1: RDD[Int] = dataRDD.mapPartitions(
         datas => { 
            datas.filter(_==2) 
         } 
     )
思考一个问题:map 和 mapPartitions 的区别?
➢ 数据处理角度:
     Map 算子是分区内一个数据一个数据的执行,类似于串行操作。而 mapPartitions 算子 是以分区为单位进行批处理操作。
 ➢ 功能的角度:
     Map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。 MapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变, 所以可以增加或减少数据 
➢ 性能的角度:
    Map 算子因为类似于串行操作,所以性能比较低,而是 mapPartitions 算子类似于批处 理,所以性能较高。但是 mapPartitions 算子会长时间占用内存,那么这样会导致内存可能 不够用,出现内存溢出的错误。所以在内存有限的情况下,不推荐使用。使用 map 操作。
    自己的理解:map是一个一个传递过来执行,但是mapPartitions是以一个区传递过来的,然后在一个区执行完后,并不会释放内存,而是等第二个区数据过来后执行完全部分区才会释放(占内存)。
    3) mapPartitionsWithIndex
 ➢ 函数说明: 将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
val dataRDD1 = dataRDD.mapPartitionsWithIndex( 
    (index, iter) => { 
        iter.map(
            num => {
                (index,num)
            }
        ) 
    } 
)
//查看每个数据在哪个分区(分区,数据)
    4)flatMap
➢ 函数说明:将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
扁平化:整体拆分成个体
val dataRDD = sc.makeRDD(List(
  List(1,2),List(3,4),2
),1)
val dataRDD1 = dataRDD.flatMap(
  list =>{
        list match {
          case list: List[_] => list
          case num => List(num)
        }
  }
)
dataRDD1.collect().foreach(println)
//比如有些是整体有些是个体,可以用模式匹配把个体变成整体。
     5)glom
➢ 函数说明:将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
和flatMap作用相反,将个体变成整体。
val dataRDD = sparkContext.makeRDD(List(
  1,2,3,4
),1)
val dataRDD1:RDD[Array[Int]] = dataRDD.glom()
//将一个list集合先转换成int,然后再变成一个Array集合类型。
    6) groupBy
➢ 函数说明:将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为 shuffle。极限情况下,数据可能被分在同一个分区中一个组的数据在一个分区中,但是并不是说一个分区中只有一个组
val dataRDD = sparkContext.makeRDD(List(1,2,3,4),1)
val dataRDD1 = dataRDD.groupBy(
  _%2
)
//groupBy方法里传递一个规则,规则可以是一个方法,也可以是规则,groupBy不会跨分区。最后返回的数据是
(规则,迭代器)如下:
    7)filter
➢ 函数说明:将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。 当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
val dataRDD = sparkContext.makeRDD(List(
  1,2,3,4
),1)
val dataRDD1 = dataRDD.filter(_%2 == 0)
//filter会匹配所有的数据,当满足条件的才会留下来
    8) sample
➢ 函数说明: 根据指定的规则从数据集中抽取数据
val dataRDD = sparkContext.makeRDD(List(
  1,2,3,4
),1)
// 抽取数据不放回(伯努利算法)
// 伯努利算法:又叫 0、1 分布。例如扔硬币,要么正面,要么反面。
// 具体实现:根据种子和随机算法算出一个数和第二个参数设置几率比较,小于第二个参数要,大于不
// 第一个参数:抽取的数据是否放回,false:不放回
// 第二个参数:抽取的几率,范围在[0,1]之间,0:全不取;1:全取;
// 第三个参数:随机数种子
val dataRDD1 = dataRDD.sample(false, 0.5)
// 抽取数据放回(泊松算法)
// 第一个参数:抽取的数据是否放回,true:放回;false:不放回
// 第二个参数:重复数据的几率,范围大于等于 0.表示每一个元素被期望抽取到的次数
// 第三个参数:随机数种子
val dataRDD2 = dataRDD.sample(true, 2)
    9)distinct
➢ 函数说明: 将数据集中重复的数据去重
    底层源码实现:
Spark-core的RDD算子总结_第1张图片
先用map转换格式(数据,null),然后reduceByKey聚合,变成(数据,(null,null,null,......)),然后再变成(数据,null),最后再map转换格式只取第一个数据。
代码:
val dataRDD = sparkContext.makeRDD(List(
  1,2,3,4,1,2
),1)
val dataRDD1 = dataRDD.distinct()
val dataRDD2 = dataRDD.distinct(2)
    10) coalesce
➢ 函数说明: 根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率 当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少分区的个数,减小任务调度成本。
白话文(将数据筛选的时候,原本的数据很大分了很多的分区,然后筛选后有很多个分区,但是分区里面的数据很少,这个时候就要将分区”合并“,减小任务调度的压力。)
val dataRDD = sparkContext.makeRDD(List(
  1,2,3,4,1,2
),6)
val dataRDD1 = dataRDD.coalesce(2)
//coalese方法的参数有两个第一个为想要变成几个分区,第二个参数为是否打乱分区并执行shuffle,如果不填的话,默认情况为false。
思考一个问题:我想要扩大分区,怎么办?
还是使用的coalesce这个方法, 但是!注意!一定要进行shuffle操作,也就是说第二个参数一定要变成true,才能扩大分区,还有一个方法就是使用repartition。其底层就是用的coalesce。只不过repartition方法,一定会进行shuffle操作。
    11)repartition
➢ 函数说明: 该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true。无论是将分区数多的 RDD 转换为分区数少的 RDD,还是将分区数少的 RDD 转换为分区数多的 RDD,repartition 操作都可以完成,因为无论如何都会经 shuffle 过程。
val dataRDD = sparkContext.makeRDD(List(
  1,2,3,4,1,2
),2)
val dataRDD1 = dataRDD.repartition(4)
//repartition在第10个的思考题有详细的解释!
    12) sortBy
➢ 函数说明: 该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理 的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原 RDD 的分区数一 致。中间存在 shuffle 的过程
val dataRDD = sparkContext.makeRDD(List(
  1,2,3,4,1,2
),2)
val dataRDD1 = dataRDD.sortBy(num=>num, false, 4)
//三个参数,第一个为按照什么排序,第二个为升序还是降序(默认为升序排序),第三个为分区数。
  • 双 Value 类型
    13) intersection
➢ 函数说明 :对源 RDD 和参数 RDD 求 交集后返回一个新的 RDD
val dataRDD1 = sparkContext.makeRDD(List(1,2,3,4))
val dataRDD2 = sparkContext.makeRDD(List(3,4,5,6))
val dataRDD = dataRDD1.intersection(dataRDD2)
    14) union
➢ 函数说明 :对源 RDD 和参数 RDD 求 并集后返回一个新的 RDD
val dataRDD1 = sparkContext.makeRDD(List(1,2,3,4))
val dataRDD2 = sparkContext.makeRDD(List(3,4,5,6))
val dataRDD = dataRDD1.union(dataRDD2)
    15) subtract
➢ 函数说明 :以一个 RDD 元素为主,去除两个 RDD 中重复元素,将其他元素保留下来。求 差集
val dataRDD1 = sparkContext.makeRDD(List(1,2,3,4))
val dataRDD2 = sparkContext.makeRDD(List(3,4,5,6))
val dataRDD = dataRDD1.subtract(dataRDD2)
//dataRDD1中3和4与dataRDD2重了,所以去掉,余下1和2为结果
    ps:交并差三个都要保持数据类型一样
    16) zip
➢ 函数说明: 将两个 RDD 中的元素,以键值对的形式进行合并。其中,键值对中的 Key 为第 1 个 RDD 中的元素,Value 为第 2 个 RDD 中的相同位置的元素。
// TODO 算子 - 双Value类型
// Can't zip RDDs with unequal numbers of partitions: List(2, 4)
// 两个数据源要求分区数量要保持一致
// Can only zip RDDs with same number of elements in each partition
// 两个数据源要求分区中数据数量保持一致
val rdd1 = sc.makeRDD(List(1,2,3,4,5,6),2)
val rdd2 = sc.makeRDD(List(3,4,5,6),2)
val rdd6: RDD[(Int, Int)] = rdd1.zip(rdd2)
println(rdd6.collect().mkString(","))
//(拉链函数)就像拉链一样,必须保持分区数相同,分区中的数量相同。
  • Key - Value 类型
注意!!想使用这些方法,一定要是键值对类型
    17) partitionBy
➢ 函数说明 :将数据按照指定 Partitioner 重新进行分区。Spark 默认的分区器是 HashPartitioner
白话文(按照传递的规则来分区)
partitionBy与coalesce和repartition的区别,partitionBy是按照规则分好数据,并将数据存入分区。coalesce和repartition是
代码:
val rdd = sc.makeRDD(List(1,2,3,4),2)
val mapRDD:RDD[(Int, Int)] = rdd.map((_,1))
// RDD => PairRDDFunctions
// 隐式转换(二次编译)
// partitionBy根据指定的分区规则对数据进行重分区
val newRDD = mapRDD.partitionBy(new HashPartitioner(2))
newRDD.partitionBy(new HashPartitioner(2))
思考一个问题:如果重分区的分区器和当前 RDD 的分区器一样怎么办
答:会比对分区的数量是否相同,不相同就正常执行,相同就什么都不会做!
思考一个问题:Spark 还有其他分区器吗? 
答:HashPartitioner和RangePartitioner和PythonPartitioner(只有特定的包才能用,有锁)
思考一个问题:如果想按照自己的方法进行数据分区怎么办? 
答:自己写一个分区器(模仿HashPatitioner)
     18) reduceByKey
➢ 函数说明 :可以将数据按照相同的 Key 对 Value 进行聚合
val dataRDD1 = sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = dataRDD1.reduceByKey(_+_)
val dataRDD3 = dataRDD1.reduceByKey(_+_, 2)
//reduceByKey就像是两步一样,聚合和合并。聚合的中间产物如:(“a”,(1,2,3,4)),合并是按照特定的规则对value这个迭代器操作
    19) groupByKey
➢ 函数说明 :将数据源的数据根据 key 对 value 进行分组
val rdd = sc.makeRDD(List(
  ("a", 1), ("a", 2), ("a", 3), ("b", 4)
))
// groupByKey : 将数据源中的数据,相同key的数据分在一个组中,形成一个对偶元组
//              元组中的第一个元素就是key,
//              元组中的第二个元素就是相同key的value的集合
val groupRDD: RDD[(String, Iterable[Int])] = rdd.groupByKey()
groupRDD.collect().foreach(println)
val groupRDD1: RDD[(String, Iterable[(String, Int)])] = rdd.groupBy(_._1)
//groupByKey和groupBy的区别在于groupByKey是只对key分组,而groupBy可以对任意分组
思考一个问题:reduceByKey 和 groupByKey 的区别?
从 shuffle 的角度:reduceByKey 和 groupByKey 都存在 shuffle 的操作,但是 reduceByKey 可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,这样会减少落盘的 数据量,而 groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较 高。 从功能的角度:reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚 合,所以在分组聚合的场合下,推荐使用 reduceByKey,如果仅仅是分组而不需要聚合。那 么还是只能使用 groupByKey
自己的理解:groupByKey和groupBy的区别在于groupByKey是只对key分组,而groupBy可以对任意分组。
     20) aggregateByKey
➢ 函数说明 :将数据根据不同的规则进行分区内计算和分区间计算
val dataRDD1 =
  sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 =
  dataRDD1.aggregateByKey(0)(_+_,_+_)
//函数表达式为 rdd.aggregateByKey(初始值)(分区内计算规则)(分区间计算规则)
//分区内初始值是用做第0个数据与第一个数据做对比的(不抛弃第一个数据),然后第一个和第二个对比,以此类推。
//分区间就是按照分区间的计算规则进行了
RDD 行动算子:
 所谓的行动算子,其实就是 触发作业(Job)执行的方法,底层代码调用的是环境对象的runJob方法, 底层代码中会创建ActiveJob,并提交执行。
只有行动算子才会有结果,转换算子只是生成新的RDD
     1) reduce(聚合)
➢ 函数说明 :聚集 RDD 中的所有元素,先聚合分区内数据,再聚合分区间数据
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 聚合数据
val reduceResult: Int = rdd.reduce(_+_)
//就是简单的聚合
    2) collect(采集)
➢ 函数说明 :在驱动程序中,以数组 Array 的形式返回数据集的所有元素。
// collect : 方法会将不同分区的数据按照分区顺序采集到Driver端内存中,形成数组
//val ints: Array[Int] = rdd.collect()
//println(ints.mkString(","))
     3) count(个数)
➢ 函数说明 :返回 RDD 中元素的个数。
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 返回 RDD 中元素的个数
val countResult: Long = rdd.count()
     4) first (首个)
➢ 函数说明 : 返回 RDD 中的第一个元素。
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 返回 RDD 中元素的个数
val firstResult: Int = rdd.first()
println(firstResult)
     5) take
➢ 函数说明 : 返回一个由 RDD 的前 n 个元素组成的数组
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 返回 RDD 中元素的个数
val takeResult: Array[Int] = rdd.take(2)
println(takeResult.mkString(","))
//取前n个数,n为参数,最终返回一个数组!
     6) takeOrdered
➢ 函数说明 :返回该 RDD 排序后的前 n 个元素组成的数组。
val rdd: RDD[Int] = sc.makeRDD(List(1,3,2,4))
// 返回 RDD 中元素的个数
val result: Array[Int] = rdd.takeOrdered(2)
//数据排序后,取n个数据,就是比take多了个排序的操作
     7) aggregate
➢ 函数说明 :分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 8)
// 将该 RDD 所有元素相加得到结果
//val result: Int = rdd.aggregate(0)(_ + _, _ + _)
val result: Int = rdd.aggregate(10)(_ + _, _ + _
//函数表达式为 rdd.aggregate(初始值)(分区内计算规则)(分区间计算规则)
//初始值是用做第0个数据与第一个数据做对比的(不抛弃第一个数据),然后第一个和第二个对比,以此类推。
//它和转换算子aggregateByKey的区别在于它直接出结果的,转换算子是只给出rdd的。
// aggregateByKey : 初始值只会参与分区内计算
// aggregate : 初始值会参与分区内计算,并且和参与分区间计算
     8) fold
➢ 函数说明 折叠操作,aggregate 的简化版操作,分区间和分区内操作相同的aggregate。
     9) countByValue
➢ 函数说明:对数据进行统计次数,就像wordcount一样,不过word是数据而已。注意这里的value是单值类型,不是key-value的value。
          countByKey
➢ 函数说明 统计每种 key 的个数
val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (1, "a"), (1, "a"), (2,
  "b"), (3, "c"), (3, "c")))
// 统计每种 key 的个数
val result: collection.Map[Int, Long] = rdd.countByKey()
     10) save 相关算子
➢ 函数说明 :将数据保存到不同格式的文件中
rdd.saveAsTextFile("output")
rdd.saveAsObjectFile("output1")
// saveAsSequenceFile方法要求数据的格式必须为K-V类型
rdd.saveAsSequenceFile("output2")
//基本都是用的第一个
     11) foreach
➢ 函数说明 :分布式遍历 RDD 中的每一个元素,调用指定函数
// foreach 其实是Driver端内存集合的循环遍历方法
rdd.collect().foreach(println)
println("******************")
// foreach 其实是Executor端内存数据打印
rdd.foreach(println)
//第一个是先收集了才去打印的,第二个是分布式打印,不收集!直接在executur打印
// 算子 : Operator(操作)
//         RDD的方法和Scala集合对象的方法不一样
//         集合对象的方法都是在同一个节点的内存中完成的。
//         RDD的方法可以将计算逻辑发送到Executor端(分布式节点)执行
//         为了区分不同的处理效果,所以将RDD的方法称之为算子。
//         RDD的方法外部的操作都是在Driver端执行的,而方法内部的逻辑代码是在Executor端执行。
//rdd的方法叫算子,scala的方法就是方法,为什么要区分呢,因为rdd的方法可以放在分布式上完成,而scala的只能在同一块内存中完成。
RDD 序列化
1) 闭包检查
     从计算的角度, 算子以外的代码都是在 Driver 端执行, 算子里面的代码都是在 Executor 端执行。那么在 scala 的函数式编程中,就会导致算子内经常会用到算子外的数据,这样就 形成了闭包的效果,如果使用的算子外的数据无法序列化,就意味着无法传值给 Executor 端执行,就会发生错误,所以需要在执行任务计算前,检测闭包内的对象是否可以进行序列 化,这个操作我们称之为闭包检测。Scala2.12 版本后闭包编译方式发生了改变。(意思就是说你要传递外面的数据的话一定要使他能序列化,不管你用没用到数据,你都要序列化,否则报错)

你可能感兴趣的:(spark,big,data,hadoop)