• Spark 学习(三) RDD基本介绍


    一,RDD概述

      1.1 什么是RDD

      1.2 RDD的属性

    二,创建RDD

    三,RDD编程API

      3.1 Transformation

      3.2 Action

      3.3 WordCount

      3.4 练习

    四,RDD的依赖关系

      4.1 窄依赖

      4.2 宽依赖

      4.3 Lineage

     

    正文

    一,RDD概述

      1.1 什么是RDD 

      RDD(Resilient Distributed Dataset)叫做分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。

      1.2 RDD的属性

      1)一组分片(Partition),即数据集的基本组成单位。对于RDD来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认值。默认值就是程序所分配到的CPU Core的数目。

      2)一个计算每个分区的函数。Spark中RDD的计算是以分片为单位的,每个RDD都会实现compute函数以达到这个目的。compute函数会对迭代器进行复合,不需要保存每次计算的结果。

      3)RDD之间的依赖关系。RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算。

      4)一个Partitioner,即RDD的分片函数。当前Spark中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有对于于key-value的RDD,才会有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量。

      5)一个列表,存储存取每个Partition的优先位置(preferred location)。对于一个HDFS文件来说,这个列表保存的就是每个Partition所在的块的位置。按照“移动数据不如移动计算”的理念,Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。

      1.3 WordCount的RDD简单图解

      hellow.txt

    二,创建RDD 

      1)由一个已经存在的Scala集合创建。

      实例:

    val rdd1 = sc.parallelize(Array(1,2,3,4,5,6,7,8))

      2)由外部存储系统的数据集创建,包括本地的文件系统,还有所有Hadoop支持的数据集,比如HDFS、Cassandra、HBase等

    val rdd2 = sc.textFile("hdfs://node1.edu360.cn:9000/words.txt")

    三,RDD编程API

      3.1 Transformation

      RDD中的所有转换都是延迟加载的,也就是说,它们并不会直接计算结果。相反的,它们只是记住这些应用到基础数据集(例如一个文件)上的转换动作。只有当发生一个要求返回结果给Driver的动作时,这些转换才会真正运行。这种设计让Spark更加有效率地运行.

      常用的Transformation:

    转换

    含义

    map(func)

    返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成

    filter(func)

    返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成

    flatMap(func)

    类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)

    mapPartitions(func)

    类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]

    mapPartitionsWithIndex(func)

    类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是

    (Int, Interator[T]) => Iterator[U]

    sample(withReplacement, fraction, seed)

    根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子

    union(otherDataset)

    对源RDD和参数RDD求并集后返回一个新的RDD

    intersection(otherDataset)

    对源RDD和参数RDD求交集后返回一个新的RDD

    distinct([numTasks]))

    对源RDD进行去重后返回一个新的RDD

    groupByKey([numTasks])  

    在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD

    reduceByKey(func, [numTasks])

    在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置

    aggregateByKey(zeroValue)(seqOp, combOp, [numTasks])

    sortByKey([ascending], [numTasks])

    在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD

    sortBy(func,[ascending], [numTasks])

    与sortByKey类似,但是更灵活

    join(otherDataset, [numTasks])

    在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD

    cogroup(otherDataset, [numTasks])

    在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的RDD

    cartesian(otherDataset)

    笛卡尔积

    pipe(command, [envVars])

    coalesce(numPartitions)     

    repartition(numPartitions)

    repartitionAndSortWithinPartitions(partitioner)

      3.2 Action

      Action就是会发生结果返回的操作。

      常见的Action:

    动作

    含义

    reduce(func)

    通过func函数聚集RDD中的所有元素,这个功能必须是课交换且可并联的

    collect()

    在驱动程序中,以数组的形式返回数据集的所有元素

    count()

    返回RDD的元素个数

    first()

    返回RDD的第一个元素(类似于take(1))

    take(n)

    返回一个由数据集的前n个元素组成的数组

    takeSample(withReplacement,num, [seed])

    返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子

    takeOrdered(n[ordering])

    saveAsTextFile(path)

    将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本

    saveAsSequenceFile(path

    将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。

    saveAsObjectFile(path

    countByKey()

    针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。

    foreach(func)

    在数据集的每一个元素上,运行函数func进行更新。

      3.3 WordCount

      Scala实现:

    sc.textFile("hdfs://node-4:9000/wc").flatMap(_.split(" ")).map((_, 1)).reduceByKey(_+_).sortBy(_._2, false).collect

      Spark中WordCount流程图:

      

      3.4 练习

    启动spark-shell
    /usr/local/spark-1.5.2-bin-hadoop2.6/bin/spark-shell --master spark://node1.edu360.cn:7077 
    
    练习1:
    //通过并行化生成rdd
    val rdd1 = sc.parallelize(List(5, 6, 4, 7, 3, 8, 2, 9, 1, 10))
    //对rdd1里的每一个元素乘2然后排序
    val rdd2 = rdd1.map(_ * 2).sortBy(x => x, true)
    //过滤出大于等于十的元素
    val rdd3 = rdd2.filter(_ >= 10)
    //将元素以数组的方式在客户端显示
    rdd3.collect
    
    练习2:
    val rdd1 = sc.parallelize(Array("a b c", "d e f", "h i j"))
    //将rdd1里面的每一个元素先切分在压平
    val rdd2 = rdd1.flatMap(_.split(' '))
    rdd2.collect
    
    练习3:
    val rdd1 = sc.parallelize(List(5, 6, 4, 3))
    val rdd2 = sc.parallelize(List(1, 2, 3, 4))
    //求并集
    val rdd3 = rdd1.union(rdd2)
    //求交集
    val rdd4 = rdd1.intersection(rdd2)
    //去重
    rdd3.distinct.collect
    rdd4.collect
    
    练习4:
    val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 3), ("kitty", 2)))
    val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 1), ("shuke", 2)))
    //求jion
    val rdd3 = rdd1.join(rdd2)
    rdd3.collect
    //求并集
    val rdd4 = rdd1 union rdd2
    //按key进行分组
    rdd4.groupByKey
    rdd4.collect
    
    练习5:
    val rdd1 = sc.parallelize(List(("tom", 1), ("tom", 2), ("jerry", 3), ("kitty", 2)))
    val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 1), ("shuke", 2)))
    //cogroup
    val rdd3 = rdd1.cogroup(rdd2)
    //注意cogroup与groupByKey的区别
    rdd3.collect
    
    练习6:
    val rdd1 = sc.parallelize(List(1, 2, 3, 4, 5))
    //reduce聚合
    val rdd2 = rdd1.reduce(_ + _)
    rdd2.collect
    
    练习7:
    val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 3), ("kitty", 2),  ("shuke", 1)))
    val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 3), ("shuke", 2), ("kitty", 5)))
    val rdd3 = rdd1.union(rdd2)
    //按key进行聚合
    val rdd4 = rdd3.reduceByKey(_ + _)
    rdd4.collect
    //按value的降序排序
    val rdd5 = rdd4.map(t => (t._2, t._1)).sortByKey(false).map(t => (t._2, t._1))
    rdd5.collect
    View Code
      3.5 RDD高级API
    http://homepage.cs.latrobe.edu.au/zhe/ZhenHeSparkRDDAPIExamples.html
    
    mapPartitionsWithIndex
    val func = (index: Int, iter: Iterator[(String)]) => {
      iter.map(x => "[partID:" +  index + ", val: " + x + "]")
    }
    
    // 可以得到每个数据所在的分区
    mapPartitionsWithIndex
    val func = (index: Int, iter: Iterator[Int]) => {
      iter.map(x => "[partID:" +  index + ", val: " + x + "]")
    }
    val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7,8,9), 2)
    rdd1.mapPartitionsWithIndex(func).collect
    // 输出如下:
    res21: Array[String] = Array([partID:0, val: 1], [partID:0, val: 2], [partID:0, val: 3], [partID:0, val: 4],
     [partID:1, val: 5], [partID:1, val: 6], [partID:1, val: 7], [partID:1, val: 8], [partID:1, val: 9])
    
    // 分区,不过是先对单个分区聚会,然后再进行全局聚合
    aggregate
    1.
    val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7,8,9), 2)
    rdd1.aggregate(0)(math.max(_, _), _ + _)  // 13  // 分为两个区 第一个分区最大值 4 第二个 9 相加 13
    rdd1.aggregate(5)(math.max(_, _), _ + _)  // 19  // 因为前面有个初始值,判断最大值会考虑改值:
                                                     //所以第分区最大值分别是:5 9,在相加的时候也会考虑初始值所以答案是19
    2.
    val rdd2 = sc.parallelize(List("a","b","c","d","e","f"),2) 
    rdd2.aggregate("")(_ + _, _ + _)
    // res2: String = defabc  res3: String = abcdef 出现两种结果,是因为分为两个分区,计算的时候是同步计算,但顺序是不确定的
    rdd2.aggregate("=")(_ + _, _ + _)
    // ==def=abc 添加了初始值
    
    3.
    val rdd3 = sc.parallelize(List("12","23","345","4567"),2)
    rdd3.aggregate("")((x,y) => math.max(x.length, y.length).toString, (x,y) => x + y) // 42 或 24
    val rdd4 = sc.parallelize(List("12","23","345",""),2)
    rdd4.aggregate("")((x,y) => math.min(x.length, y.length).toString, (x,y) => x + y)  // 10 或 01
    // 执行逻辑:"".length  12.length ---> 0, 2
                 0.length   23.length ---> 1, 2  
        第一组:1
                 "".length  345.length ---> 0, 3
                 0.length   "".length ----> 1, 0
        第二组:0 
        所以最终结果会出现:10 或 01
    val rdd5 = sc.parallelize(List("12","23","","345"),2)
    rdd5.aggregate("")((x,y) => math.min(x.length, y.length).toString, (x,y) => x + y) // 11
    // 执行逻辑:"".length  12.length ---> 0, 2
                 0.length   23.length ---> 1, 2  
        第一组:1
                 "".length  "".length ---> 0, 0
                 0.length   345.length ----> 1, 3
        第二组:1 
        所以最终结果会出现:11
    
    aggregateByKey
    1.
    val pairRDD = sc.parallelize(List( ("cat",2), ("cat", 5), ("mouse", 4),("cat", 12), ("dog", 12), ("mouse", 2)), 2)
    pairRDD.aggregateByKey(0)(math.max(_, _), _ + _).collect
    // Array[(String, Int)] = Array((dog,12), (cat,17), (mouse,6))  对key聚合
    pairRDD.aggregateByKey(100)(math.max(_, _), _ + _).collect
    第一组:cat(2, 5, 100) mouse(4, 100)         第二组:cat(12 100)  dog(12, 100)  mouse(12, 100)
    // 先局部聚会后再聚合
    // Array[(String, Int)] = Array((dog,100), (cat,200), (mouse,200))
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    checkpoint
    sc.setCheckpointDir("hdfs://node-1.edu360.cn:9000/ck")
    val rdd = sc.textFile("hdfs://node-1.edu360.cn:9000/wc").flatMap(_.split(" ")).map((_, 1)).reduceByKey(_+_)
    rdd.checkpoint
    rdd.isCheckpointed
    rdd.count
    rdd.isCheckpointed
    rdd.getCheckpointFile
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    coalesce, repartition
    val rdd1 = sc.parallelize(1 to 10, 10)
    val rdd2 = rdd1.coalesce(2, false) // 指定分区
    rdd2.partitions.length  // 2 查看分区数量
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    // 将集合变成map
    collectAsMap
    val rdd = sc.parallelize(List(("a", 1), ("b", 2)))
    rdd.collectAsMap // scala.collection.Map[String,Int] = Map(b -> 2, a -> 1)
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    combineByKey
    val rdd1 = sc.textFile("hdfs://node-1.edu360.cn:9000/wc").flatMap(_.split(" ")).map((_, 1))
    val rdd2 = rdd1.combineByKey(x => x, (a: Int, b: Int) => a + b, (m: Int, n: Int) => m + n)
    rdd2.collect
    
    val rdd3 = rdd1.combineByKey(x => x + 10, (a: Int, b: Int) => a + b, (m: Int, n: Int) => m + n)
    rdd3.collect
    
    
    val rdd4 = sc.parallelize(List("dog","cat","gnu","salmon","rabbit","turkey","wolf","bear","bee"), 3)
    val rdd5 = sc.parallelize(List(1,1,2,2,2,1,2,2,2), 3)
    val rdd6 = rdd5.zip(rdd4)
    val rdd7 = rdd6.combineByKey(List(_), (x: List[String], y: String) => x :+ y, (m: List[String], n: List[String]) => m ++ n)
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    countByKey 
    
    val rdd1 = sc.parallelize(List(("a", 1), ("b", 2), ("b", 2), ("c", 2), ("c", 1)))
    rdd1.countByKey // 统计相同Key的个数  scala.collection.Map[String,Long] = Map(b -> 2, c -> 2, a -> 1)
    rdd1.countByValue // 对整组数据进行计数scala.collection.Map[(String, Int),Long] = Map((c,2) -> 1, (c,1) -> 1, (b,2) -> 2, (a,1) -> 1)
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    filterByRange  // 根据key的范围取值
    val rdd1 = sc.parallelize(List(("e", 5), ("c", 3), ("d", 4), ("c", 2), ("a", 1)))
    val rdd2 = rdd1.filterByRange("b", "d")  
    rdd2.colllect //  Array[(String, Int)] = Array((c,3), (d,4), (c,2))
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    flatMapValues  // 对value进行拆分压缩
    val rdd3 = sc.parallelize(List(("a", "1 2"), ("b", "3 4")))
    rdd3.flatMapValues(_.split(" "))  //  // Array[(String, String)] = Array((a,1), (a,2), (b,3), (b,4))
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    foldByKey  // 更具key聚合
    val rdd1 = sc.parallelize(List("dog", "wolf", "cat", "bear"), 2)
    val rdd2 = rdd1.map(x => (x.length, x))  //  Array[(Int, String)] = Array((3,dog), (4,wolf), (3,cat), (4,bear))
    val rdd3 = rdd2.foldByKey("")(_+_)  // Array[(Int, String)] = Array((4,wolfbear), (3,catdog))
    
    val rdd = sc.textFile("hdfs://node-1.edu360.cn:9000/wc").flatMap(_.split(" ")).map((_, 1))
    rdd.foldByKey(0)(_+_)
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    foreachPartition  // 迭代数据
    val rdd1 = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9), 3)
    rdd1.foreachPartition(x => println(x.reduce(_ + _)))
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    keyBy // 生成key
    val rdd1 = sc.parallelize(List("dog", "salmon", "salmon", "rat", "elephant"), 3)
    val rdd2 = rdd1.keyBy(_.length)
    rdd2.collect // Array[(Int, String)] = Array((3,dog), (6,salmon), (6,salmon), (3,rat), (8,elephant))
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    keys values
    val rdd1 = sc.parallelize(List("dog", "tiger", "lion", "cat", "panther", "eagle"), 2)
    val rdd2 = rdd1.map(x => (x.length, x))
    rdd2.keys.collect // 获取key
    rdd2.values.collect // 获取value
    
    -------------------------------------------------------------------------------------------
    -------------------------------------------------------------------------------------------
    mapPartitions( it: Iterator => {it.map(x => x * 10)})
    View Code

    四,RDD的依赖关系

      RDD和它依赖的父RDD(s)的关系有两种不同的类型,即窄依赖(narrow dependency)和宽依赖(wide dependency)。

      如下图所示:左边是窄依赖,右边是宽依赖

      

      shuffle重要的依据:父RDD的一个分区的数据,要给子RDD的多个分区

      (1)图中左半部分join:如果两个RDD在进行join操作时,一个RDD的partition仅仅和另一个RDD中已知个数的Partition进行join,那么这种类型的join操作就是窄依赖,例如图1中左半部分的join操作(join with inputs co-partitioned);

      (2)图中右半部分join:其它情况的join操作就是宽依赖,例如图1中右半部分的join操作(join with inputs not co-partitioned),由于是需要父RDD的所有partition进行join的转换,这就涉及到了shuffle,因此这种类型的join操作也是宽依赖。

      4.1 窄依赖

      窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用

      总结:窄依赖我们形象的比喻为独生子女

      4.2 宽依赖

      宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition

      总结:窄依赖我们形象的比喻为超生

      4.3 Lineage

      RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列Lineage(即血统)记录下来,以便恢复丢失的分区。RDD的Lineage会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。

      

       4.5 总结

      在这里我们是从父RDD的partition被使用的个数来定义窄依赖和宽依赖,因此可以用一句话概括下:如果父RDD的一个Partition被子RDD的一个Partition所使用就是窄依赖,否则的话就是宽依赖。因为是确定的partition数量的依赖关系,所以RDD之间的依赖关系就是窄依赖;由此我们可以得出一个推论:即窄依赖不仅包含一对一的窄依赖,还包含一对固定个数的窄依赖。

      一对固定个数的窄依赖的理解:即子RDD的partition对父RDD依赖的Partition的数量不会随着RDD数据规模的改变而改变;换句话说,无论是有100T的数据量还是1P的数据量,在窄依赖中,子RDD所依赖的父RDD的partition的个数是确定的,而宽依赖是shuffle级别的,数据量越大,那么子RDD所依赖的父RDD的个数就越多,从而子RDD所依赖的父RDD的partition的个数也会变得越来越多。

      4.6 依赖关系流程图

      

      

      在spark中,会根据RDD之间的依赖关系将DAG图(有向无环图)划分为不同的阶段,对于窄依赖,由于partition依赖关系的确定性,partition的转换处理就可以在同一个线程里完成,窄依赖就被spark划分到同一个stage中,而对于宽依赖,只能等父RDD shuffle处理完成后,下一个stage才能开始接下来的计算。

      因此spark划分stage的整体思路是:从后往前推,遇到宽依赖就断开,划分为一个stage;遇到窄依赖就将这个RDD加入该stage中。因此在图2中RDD C,RDD D,RDD E,RDDF被构建在一个stage中,RDD A被构建在一个单独的Stage中,而RDD B和RDD G又被构建在同一个stage中。

    在spark中,Task的类型分为2种:ShuffleMapTaskResultTask

      简单来说,DAG的最后一个阶段会为每个结果的partition生成一个ResultTask,即每个Stage里面的Task的数量是由该Stage中最后一个RDD的Partition的数量所决定的!而其余所有阶段都会生成ShuffleMapTask;之所以称之为ShuffleMapTask是因为它需要将自己的计算结果通过shuffle到下一个stage中;也就是说上图中的stage1和stage2相当于mapreduce中的Mapper,而ResultTask所代表的stage3就相当于mapreduce中的reducer。

      在之前动手操作了一个wordcount程序,因此可知,Hadoop中MapReduce操作中的Mapper和Reducer在spark中的基本等量算子是map和reduceByKey;不过区别在于:Hadoop中的MapReduce天生就是排序的;而reduceByKey只是根据Key进行reduce,但spark除了这两个算子还有其他的算子;因此从这个意义上来说,Spark比Hadoop的计算算子更为丰富。

  • 相关阅读:
    win10安装vue
    通过docker安装rabbitmq
    python定时访问主机进程信息并反馈到远程服务器
    win10配置Apache+部署静态html页面
    HTTP协议—HTTP报文格式详解
    python脚本编写(纯干货)
    linux/CentOS的安装(萌新版)
    gradle中调用ant.unzip对zip包解压缩
    通过gradle运行测试脚本
    使用rest-assured测试rest service, 遇到“415 Unsupported Media Type”
  • 原文地址:https://www.cnblogs.com/tashanzhishi/p/10975760.html
Copyright © 2020-2023  润新知