spark RDD概念及组成详解

概念

弹性分布式数据集(RDD),Spark中的基本抽象。表示可以并行操作的元素的不变分区集合。此类包含所有RDD上可用的基本操作,例如mapfilterpersist。另外,PairRDDFunctions包含仅在k-v对的RDD上可用的操作,例如groupByKey和join; DoubleRDDFunctions包含仅在Doubles的RDD上可用的操作;和SequenceFileRDDFunctions包含RDD上可用的操作,这些操作可以另存为SequenceFiles

在内部,每个RDD具有五个主要属性:

A list of partitions
A function for computing each split
A list of dependencies on other RDDs
Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)

Spark中的所有调度和执行都是基于这些方法完成的,从而允许每个RDD自行实现自己的计算方式,实际上,用户可以通过覆盖这些功能来实现自定义RDD(例如,用于从新存储系统读取数据)

三大特征

1 不可变的:RDD一旦被创建,数据是只读的,如果更改数据,会产生新的RDD
2 分区:一个RDD有多个Partition,Partition也可以根据业务需求来指定
3并行的: 因为有多个分区,所以可以并行计算.

RDD的结构

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

object RDDTest  extends App{
  val conf = new SparkConf().setAppName("wordcount").setMaster("local")
  val sc = new SparkContext(conf)
  private val rdd: RDD[String] = sc.textFile("file:///D:/tmp",2)
  private val partitions: Array[Partition] = rdd.partitions
  for(x<-partitions){
    println(s"x: $x x.index: ${x.index} x.hashCode: ${x.hashCode()}")
  }
}

结果如下

x: org.apache.spark.rdd.HadoopPartition@3c1 x.index: 0 x.hashCode: 961
x: org.apache.spark.rdd.HadoopPartition@3c2 x.index: 1 x.hashCode: 962

如果做下修改

private val rdd: RDD[String] = sc.textFile("file:///D:/tmp",3)

则结果是

x: org.apache.spark.rdd.HadoopPartition@3c1 x.index: 0 x.hashCode: 961
x: org.apache.spark.rdd.HadoopPartition@3c2 x.index: 1 x.hashCode: 962
x: org.apache.spark.rdd.HadoopPartition@3c3 x.index: 2 x.hashCode: 963

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

def compute(split: Partition, context: TaskContext): Iterator[T]
//:: DeveloperApi :: Implemented by subclasses to compute a given partition.

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

object RDDTest  extends App{
  val conf = new SparkConf().setAppName("wordcount").setMaster("local")
  val sc = new SparkContext(conf)
  private val rdd: RDD[String] = sc.textFile("file:///D:/tmp",3)
  private val dependencies: Seq[Dependency[_]] = rdd.dependencies
  println(dependencies)//List(org.apache.spark.OneToOneDependency@69d45cca)
}

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

object RDDTest  extends App{
  val conf = new SparkConf().setAppName("wordcount").setMaster("local")
  val sc = new SparkContext(conf)
  private val rdd: RDD[String] = sc.textFile("file:///D:/tmp",3)
  println(rdd.partitioner)//None
  private val rdd1: RDD[(String, Int)] = rdd.map(x => (x, 1))
  println(rdd1.partitioner)//None
  //reduceByKey会调用Hash分区器进行分区
  private val rdd2: RDD[(String, Int)] = rdd1.reduceByKey(_ + _)
  println(rdd2.partitioner)//Some(org.apache.spark.HashPartitioner@3)
  private val rdd3: RDD[(String, Int)] = rdd2.repartition(4)
  println(rdd3.partitioner)//None
}

再比如

def test2(): Unit ={
    val conf: SparkConf = new SparkConf().setAppName("wordcount").setMaster("local")
    val sc = new SparkContext(conf)
    val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 6),2)
    println(rdd.partitioner)//None
    //打印Partition对象,方便看看究竟是什么,其实这个主要是存了一个分区的index
    for(x<-rdd.partitions){
      println(x.index+" "+x.toString)
    }
    val rdd2: RDD[(Int, Int)] = rdd.map(x => (x, 1))
    println(rdd2.partitioner)//None
    //reduceByKey操作会调用Hash分区器重新分区
    val rdd3: RDD[(Int, Int)] = rdd2.reduceByKey(_ + _,3)
    println(rdd3.partitioner)//Some(org.apache.spark.HashPartitioner@3)
    //打印Partition对象,方便看看究竟是什么,其实这个主要是存了一个分区的index
    //因为reduceByKey传入了分区数量,所以这个分区数量已经变为3个了,就是Partition数量变为3个
    for(x<-rdd3.partitions){
      println(x.index+" "+x.toString)
    }
  }

运行结果

None
0 org.apache.spark.rdd.ParallelCollectionPartition@691
1 org.apache.spark.rdd.ParallelCollectionPartition@692
None
Some(org.apache.spark.HashPartitioner@3)
0 org.apache.spark.rdd.ShuffledRDDPartition@0
1 org.apache.spark.rdd.ShuffledRDDPartition@1
2 org.apache.spark.rdd.ShuffledRDDPartition@2

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

def test3(): Unit ={
    val conf: SparkConf = new SparkConf().setAppName("wordcount").setMaster("local")
    val sc = new SparkContext(conf)
    val rdd: RDD[String] = sc.textFile("hdfs://qmypc01:/sparkstream",2)
    rdd.saveAsTextFile("/rddtest")
    val partitions: Array[Partition] = rdd.partitions
    for(x<-partitions){
      println(rdd.preferredLocations(x))//List()
    }
  }

然后rdd还有其他属性,比如name,id等等,都可以通过代码进行测试.

RDD弹性

  1. 自动进行内存和磁盘数据存储的切换

​ Spark优先把数据放到内存中,如果内存放不下,就会放到磁盘里面,程序进行自动的存储切换

  1. 基于血统的高效容错机制

​> 在RDD进行转换和动作的时候,会形成RDD的Lineage依赖链,当某一个RDD失效的时候,可以通过重新计算上游的RDD来重新生成丢失的RDD数据。

  1. Task如果失败会自动进行特定次数的重试

RDD的计算任务如果运行失败,会自动进行任务的重新计算,默认次数是4次。

  1. Stage如果失败会自动进行特定次数的重试

如果Job的某个Stage阶段计算失败,框架也会自动进行任务的重新计算,默认次数也是4次。

  1. Checkpoint和Persist可主动或被动触发

RDD可以通过Persist持久化将RDD缓存到内存或者磁盘,当再次用到该RDD时直接读取就行。也可以将RDD进行检查点,检查点会将数据存储在HDFS中,该RDD的所有父RDD依赖都会被移除。

  1. 数据调度弹性

Spark把这个JOB执行模型抽象为通用的有向无环图DAG,可以将多Stage的任务串联或并行执行,调度引擎自动处理Stage的失败以及Task的失败。

  1. 数据分片的高度弹性

可以根据业务的特征,动态调整数据分片的个数,提升整体的应用执行效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值