1. RDD的依赖关系

1.1 WordCount中的RDD

spark调整reducer数据量 spark reduce个数_spark

以下代码中的WordCount会生成几个RDD?

scala> val rdd1000 = sc.textFile("hdfs://hadoop01:9000/wc").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
rdd1000: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[10] at reduceByKey at <console>:27

关于wordcount中的RDD数量的计算
由 rdd1000.toDebugString 结果可知一共 产生了5个RDD

scala> rdd1000.collect
res3: Array[(String, Int)] = Array((tom,6), (world,3), (hello,15), (jerry,3), (kitty,3))

scala> rdd1000.toDebugString
res4: String = 
(3) ShuffledRDD[10] at reduceByKey at <console>:27 []
 +-(3) MapPartitionsRDD[9] at map at <console>:27 []
    |  MapPartitionsRDD[8] at flatMap at <console>:27 []
    |  hdfs://hadoop01:9000/wc MapPartitionsRDD[7] at textFile at <console>:27 []
    |  hdfs://hadoop01:9000/wc HadoopRDD[6] at textFile at <console>:27 []

下面我们来详细分析RDD 数量

object WordCount {

  def main(args: Array[String]): Unit = {
    //非常重要,是通向Spark集群的入口
    val conf = new SparkConf().setAppName("wordcount")
      .setJars(Array("D:\\IntellJIDEAProjects\\HelloSpark\\target\\HelloSpark-1.0-SNAPSHOT.jar"))
      .setMaster("spark://hadoop01:7077")
    val sc = new SparkContext(conf)
    // textFile会产生两个RDD  HadoopRDD MapPartitionsRDD
    sc.textFile(args(0))
      //产生一个RDD:MapPartitionsRDD
      .flatMap(_.split(" "))
      //产生一个RDD:MapPartitionsRDD
      .map((_,1))
      //产生一个RDD:ShuffledRDD
      .reduceByKey(_+_)
    sc.stop()
  }
}

1.2. RDD的依赖关系

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

spark调整reducer数据量 spark reduce个数_RDD缓存_02

  1. 窄依赖
    窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用
    总结:窄依赖我们形象的比喻为独生子女
  2. 宽依赖
    宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition
    总结:窄依赖我们形象的比喻为超生
  3. Lineage
    RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列Lineage(即血统)记录下来,以便恢复丢失的分区。RDD的Lineage会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。
  4. spark调整reducer数据量 spark reduce个数_RDD缓存_03

2.RDD的缓存

Spark速度非常快的原因之一,就是在不同操作中可以在内存中持久化或缓存个数据集。当持久化某个RDD后,每一个节点都将把计算的分片结果保存在内存中,并在对此RDD或衍生出的RDD进行的其他动作中重用。这使得后续的动作变得更加迅速。RDD相关的持久化和缓存,是Spark最重要的特征之一。可以说,缓存是Spark构建迭代式算法和快速交互式查询的关键。

  • RDD缓存 :cache persist,将RDD结果缓存到内存,如果内存空间不足,将放入磁盘
val rdd = sc.textFile("hdfs://hadoop01:9000/itcast").cache()   将rdd中的内容缓存到内存

rdd.map(_.split("\t")).map(x => (x(1),1)).reduceByKey(_+_).collect

 rdd.unpersist(true)     //缓存释放

2.1. RDD缓存方式

RDD通过persist方法或cache方法可以将前面的计算结果缓存,但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用。

spark调整reducer数据量 spark reduce个数_spark_04


通过查看源码发现cache最终也是调用了persist方法,默认的存储级别都是仅在内存存储一份,Spark的存储级别还有好多种,存储级别在object StorageLevel中定义的。

spark调整reducer数据量 spark reduce个数_spark调整reducer数据量_05


缓存有可能丢失,或者存储存储于内存的数据由于内存不足而被删除,RDD的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于RDD的一系列转换,丢失的数据会被重算,由于RDD的各个Partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition。

3.RDD的备份机制

RDD checkPoint机制:RDD备份机制主要为了防止某些情况下RDD内容的丢失,将RDD的结果持久化到HDFS,当这些RDD中的内容丢失时,不会根据RDD的父依赖关系再次迭代计算值,而是直接从checkpoint中还原结果

//第一步需要先设置checkpointDir
sc.setCheckpointDir("hdfs://hadoop01:9000/ck20191221")      //设置checkpoint目录  

 val rdd = sc.textFile("hdfs://hadoop01:9000/itcast")
 rdd.checkpoint    //为transformation不会立即触发,等待action后自动触发
 rdd.count      //启动两个任务一个任务执行collect操作,另一个任务执行后将结果存入checkpoint


 val rdd2 = rdd.map(_.split("\t")).map(x => (x(1),1)).reduceByKey(_+_)
 rdd2.checkpoint
 rdd2.collect

强烈建议将rdd先cache(),然后再checkpoint,这样将结果持久化到HDFS时,直接从内存读数据不用重新启动一个任务

scala> rdd2.cache()
scala> rdd2.checkpoint
scala> rdd2.collect

4. DAG的生成

DAG(Directed Acyclic Graph)叫做有向无环图,原始的RDD通过一系列的转换就就形成了DAG,根据RDD之间的依赖关系的不同将DAG划分成不同的Stage,对于窄依赖,partition的转换处理在Stage中完成计算。对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,因此宽依赖是划分Stage的依据。

spark调整reducer数据量 spark reduce个数_RDD备份_06