spark编程模型二之RDD的编程接口

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/flyinthesky111/article/details/82588108




版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/flyinthesky111/article/details/82563781


此次经验分享共分为两部分,上部分主要偏向理论介绍,下部分更偏向代码实操
此经验来源于《图解Spark核心技术与案例》一书,书挺不错的,有需要学习的可以去看看。
  咱们接着上回说的检查点继续往下说。上次主要分享了RDD的一些基本的概念及其用法。包括RDD的类型、简介、作业调度内存管理等等,还以Wordcount代码为例子带大家简单的看了一下webUI的界面,本次分享的下半部分就主要偏向实操,以代码为基础,为大家展现以下几部分知识:
    RDD的编程接口。
    RDD的创建。
    RDD的转换操作。
    RDD的控制操作。
    RDD的行动操作。
  众所周知,spark中提供的是一个通用的抽象对象来进行编码,他给这些抽象的RDD提供了以下接口来实现编程:1.分区信息,这些接口提供了数据集的最小的切片;2.依赖关系,主要指向该RDD的父RDD;3.函数,基于父RDD的计算(代码中应用最多的地方);4.划分策略和数据位置的元数据。
下面,咱们用一个表来形象的描述一下这四个接口的含义:
RDD的编程接口
   RDD的分区
  RDD的分区指的是将RDD划分成很多的分区,分布到集群中的节点去,分区的多少涉及到这个RDD的并行计算的粒度。分区是一个逻辑上的一个概念。在变幻前后的分区在物理意义上有可能在同一块内存或者存储上。在RDD的操作中用户可以使用Partitions方法获取RDD划分的分区数。而且用户也可以设置分区数,当你没有设立的时候,会有默认值,默认值就是该程序所分配的CPU核数,如果该RDD从HDFS文件系统创建,那么就是默认的文件的数据块的数量。
  举个例子:
  下面两行代码是在spark-shell中读取文件的时候,设置和不设置分区数的操作和返回结果:
不设置分区数
设置了6个分区数
  RDD的首选位置
  在spark形成有向无环图的时候,会尽可能的吧计算分配到快进数据的位置,减少数据的网络传输。所以,当RDD产生的时候会有一个首选位置,例如,HadoopRDD分区的首选位置就是HDFS块所在的节点;当RDD分区被缓存的时候,计算就会被发往数据所缓存虽在的节点进行,再不然,就会回溯RDD的血统位置,一直找到具有首选位置属性的父RDD,根据这个来确定子RDD所在的位置。
  RDD的依赖关系
  RDD的依赖关系的划分在RDD的解读中的重要性不言而喻。因为在spark中shuffle和stage的划分都是根据RDD的依赖关系来决定的。在spark中,RDD的依赖关系被分为两类:一种是窄依赖,另外一种就是宽依赖。
  窄依赖是指:每个父RDD的分区最多都只会被一个子RDD所用。
  宽依赖是指:多个子RDD的分区依赖于一个父RDD的分区。
  从另外的角度来解释:窄依赖允许在单个集群节点上流水线式的执行,这个节点可以计算所有的父级分区。相反,宽依赖需要所有父RDD的数据,并且数据需要通过类似于MR的操作shuffle完成。其次:窄依赖中,节点失败后的数据恢复会更加高效,因为只用计算丢失的父级分区,而且这些计算可以并行的在不同节点上进行计算。相反的,宽依赖中,单个节点的失败可能会导致一个RDD所有的先祖RDD一些分区丢失,会导致计算重新执行。
  下面以代码来解释一下:


scala> val rdd = sc.textFile("/root/spark/spark-2.3.1-bin-hadoop2.7/README.md")
rdd: org.apache.spark.rdd.RDD[String] = /root/spark/spark-2.3.1-bin-hadoop2.7/README.md MapPartitionsRDD[5] at textFile at <console>:24

scala> val wordMap = rdd.flatMap(_.split(" ")).map(x=>(x,1))
wordMap: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[7] at map at <console>:25

scala> println(wordMap)
MapPartitionsRDD[7] at map at <console>:25
//wordMap的依赖关系为OneToOne,也就是窄依赖
scala> wordMap.dependencies.foreach { dep => 
     | println("dependencey type is" + dep.getClass)
     | println("dependency RDD is" + dep.rdd)
     | println("dependency partitions is" + dep.rdd.partitions)
     | println("dependency partitions size is" + dep.rdd.partitions.length)
     | }
dependencey type isclass org.apache.spark.OneToOneDependency
dependency RDD isMapPartitionsRDD[6] at flatMap at <console>:25
dependency partitions is[Lorg.apache.spark.Partition;@297da2c3
dependency partitions size is2

  咱们再看一下宽依赖的类型,接着上面的代码进行:

scala> val wordReduced = wordMap.reduceByKey(_+_)
wordReduced: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[8] at reduceByKey at <console>:25

scala> println(wordReduced)
ShuffledRDD[8] at reduceByKey at <console>:25
//wordReduced 的依赖类型为shuffle类型,属于宽依赖的。
scala> wordReduced.dependencies.foreach { dep =>
     |  println("dependency type is: "+ dep.getClass)
     |  println("dependency RDD is: "+ dep.rdd)
     |  println("dependency partitions is: "+ dep.rdd.partitions)
     |  println("dependency partitions size is: "+ dep.rdd.partitions.length)
     | }
dependency type is: class org.apache.spark.ShuffleDependency
dependency RDD is: MapPartitionsRDD[7] at map at <console>:25
dependency partitions is: [Lorg.apache.spark.Partition;@297da2c3
dependency partitions size is: 2

  宽窄依赖的例子就简单举了一下,感兴趣的可以自己多尝试几种类型的操作去验证一下自己的想法
  RDD的分区计算(Iterator)
  spark中RDD 的计算是以分区为单位的,而且计算函数都是在对迭代器复合,不需要保存每次计算的结果。分区计算一般使用的是mapPartitions等操作进行,mapPartition的输入函数会应用于每个分区,也就是说他是把每一个分区的内容当做整体来进行处理的。
  分析分析下面的代码:

scala> val a = sc.parallelize(1 to 9, 3)
a: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[9] at parallelize at <console>:24

scala> def iterFunc [T](iter: Iterator[T]) : Iterator[(T,T)] = {
     | var res = List[(T,T)] ()
     | var pre = iter.next
     | while (iter.hasNext) {
     | val cur = iter.next
     | res ::=(pre,cur)
     | pre=cur
     | }
     | res.iterator
     | }
iterFunc: [T](iter: Iterator[T])Iterator[(T, T)]

scala> a.mapPartitions(iterFunc).collect
res6: Array[(Int, Int)] = Array((2,3), (1,2), (5,6), (4,5), (8,9), (7,8))       

  这个例子中,函数iterFunc的作用是把分区中一个元素和她的下一个元素组成一个Tuple,因为分区中最好一个元素没有下一个元素了,所以(3,4)和(6,7)不在结果中。
  RDD的分区函数(Partitioner)
  spark中分区的划分对于shuffle类的操作很重要,它会决定这个操作的父子RDD之间的依赖关系。就例如join操作来说,假如用协同划分,两个父RDD之间,父RDD和子RDD之间能够形成一致的分区安排,即同一个KEY能保证被映射到同一个分区,这样就是一个窄依赖。但是没有协同划分的话,就会导致宽依赖。
  spark中默认提供两种划分器:哈希分区划分器和范围分区划分器。并且,Partitoiner之存在于(K,V)类型的RDD中,对于非(K,V)类型的Partitioner的默认值是none。
  下面进行代码举例:

//这段代码首先创建了一个MapPartitionsRDD,他的partitioner为none,然后对RDD进行groupByKey得到了group_rdd对象,参数4代表其最终由4个分区。
scala> val part = sc.textFile("/root/spark/spark-2.3.1-bin/hadoop2.7/README.md")
part: org.apache.spark.rdd.RDD[String] = /root/spark/spark-2.3.1-bin/hadoop2.7/README.md MapPartitionsRDD[1] at textFile at <console>:24

scala> part.partitioner
res0: Option[org.apache.spark.Partitioner] = None

scala> val group_rdd = part.map(x=>(x,x)).groupByKey(new org.apache.spark.HashPartitioner(4))
group_rdd: org.apache.spark.rdd.RDD[(String, Iterable[String])] = ShuffledRDD[3] at groupByKey at <console>:25

scala> group_rdd.partitioner
res1: Option[org.apache.spark.Partitioner] = Some(org.apache.spark.HashPartitioner@4)

到这里,RDD的编程接口就简单的叙述了一下,下一篇将要分享的是RDD的创建和转换操作,基本上再有两篇文章分享就把spark的编程模型——RDD简单的解释完成了,如果大家需要深入了解的话,可以自行去看一下这本书,对于spark的讲解还是很详细的






版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/flyinthesky111/article/details/82563781


此次经验分享共分为两部分,上部分主要偏向理论介绍,下部分更偏向代码实操
此经验来源于《图解Spark核心技术与案例》一书,书挺不错的,有需要学习的可以去看看。
  咱们接着上回说的检查点继续往下说。上次主要分享了RDD的一些基本的概念及其用法。包括RDD的类型、简介、作业调度内存管理等等,还以Wordcount代码为例子带大家简单的看了一下webUI的界面,本次分享的下半部分就主要偏向实操,以代码为基础,为大家展现以下几部分知识:
    RDD的编程接口。
    RDD的创建。
    RDD的转换操作。
    RDD的控制操作。
    RDD的行动操作。
  众所周知,spark中提供的是一个通用的抽象对象来进行编码,他给这些抽象的RDD提供了以下接口来实现编程:1.分区信息,这些接口提供了数据集的最小的切片;2.依赖关系,主要指向该RDD的父RDD;3.函数,基于父RDD的计算(代码中应用最多的地方);4.划分策略和数据位置的元数据。
下面,咱们用一个表来形象的描述一下这四个接口的含义:
RDD的编程接口
   RDD的分区
  RDD的分区指的是将RDD划分成很多的分区,分布到集群中的节点去,分区的多少涉及到这个RDD的并行计算的粒度。分区是一个逻辑上的一个概念。在变幻前后的分区在物理意义上有可能在同一块内存或者存储上。在RDD的操作中用户可以使用Partitions方法获取RDD划分的分区数。而且用户也可以设置分区数,当你没有设立的时候,会有默认值,默认值就是该程序所分配的CPU核数,如果该RDD从HDFS文件系统创建,那么就是默认的文件的数据块的数量。
  举个例子:
  下面两行代码是在spark-shell中读取文件的时候,设置和不设置分区数的操作和返回结果:
不设置分区数
设置了6个分区数
  RDD的首选位置
  在spark形成有向无环图的时候,会尽可能的吧计算分配到快进数据的位置,减少数据的网络传输。所以,当RDD产生的时候会有一个首选位置,例如,HadoopRDD分区的首选位置就是HDFS块所在的节点;当RDD分区被缓存的时候,计算就会被发往数据所缓存虽在的节点进行,再不然,就会回溯RDD的血统位置,一直找到具有首选位置属性的父RDD,根据这个来确定子RDD所在的位置。
  RDD的依赖关系
  RDD的依赖关系的划分在RDD的解读中的重要性不言而喻。因为在spark中shuffle和stage的划分都是根据RDD的依赖关系来决定的。在spark中,RDD的依赖关系被分为两类:一种是窄依赖,另外一种就是宽依赖。
  窄依赖是指:每个父RDD的分区最多都只会被一个子RDD所用。
  宽依赖是指:多个子RDD的分区依赖于一个父RDD的分区。
  从另外的角度来解释:窄依赖允许在单个集群节点上流水线式的执行,这个节点可以计算所有的父级分区。相反,宽依赖需要所有父RDD的数据,并且数据需要通过类似于MR的操作shuffle完成。其次:窄依赖中,节点失败后的数据恢复会更加高效,因为只用计算丢失的父级分区,而且这些计算可以并行的在不同节点上进行计算。相反的,宽依赖中,单个节点的失败可能会导致一个RDD所有的先祖RDD一些分区丢失,会导致计算重新执行。
  下面以代码来解释一下:

猜你喜欢

转载自blog.csdn.net/flyinthesky111/article/details/82588108