Spark-RDD迭代器管道计算

一、上下文

Spark-Task启动流程》中讲到我们提交Stage是传入的是这个Stage最后一个RDD,当Task中触发ShuffleWriter、返回Driver数据或者写入Hadoop文件系统时才触发这个RDD调用它的iterator(),下面我们就来看下RDD.iterator()背后的故事。

二、RDD中的iterator

我们先来看下rdd.iterator()以及后面一些列的调用

Scala 复制代码
  final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
    //如果该rdd是被持久化的那么直接得到它或者根据血缘关系计算它,并返回,到这就终止了,可以直接从这往后计算了
    if (storageLevel != StorageLevel.NONE) {
      getOrCompute(split, context)
    } else {
      //调用compute()或者从Checkpoint读取数据
      computeOrReadCheckpoint(split, context)
    }
  }

  private[spark] def computeOrReadCheckpoint(split: Partition, context: TaskContext): Iterator[T] =
  {
    //如果该RDD是Checkpointed那么直接返回对应分区的迭代器,直接从这里开始计算
    if (isCheckpointedAndMaterialized) {
      firstParent[T].iterator(split, context)
    } else {
      compute(split, context)
    }
  }

  //由子类实现,用于计算给定的分区,下面我们看下重要的几个子类
  def compute(split: Partition, context: TaskContext): Iterator[T]

三、RDD子类中的compute

MapPartitionsRDD

Scala 复制代码
  //一个RDD调用 map() flatMap() filter() glom() mapPartitions() 都会返回一个 MapPartitionsRDD 且把对应的处理函数传进去
  //这个时候就会返回该函数,且里面会调用父RDD的 iterator 去拿数据来处理
  override def compute(split: Partition, context: TaskContext): Iterator[U] =
    f(context, split.index, firstParent[T].iterator(split, context))

ShuffledRDD

Scala 复制代码
  //一个RDD 调用 repartition() coalesce() sortByKey() groupByKey() reduceByKey() 时都有可能返回一个ShuffledRDD
  override def compute(split: Partition, context: TaskContext): Iterator[(K, C)] = {
    val dep = dependencies.head.asInstanceOf[ShuffleDependency[K, V, C]]
    val metrics = context.taskMetrics().createTempShuffleReadMetrics()
    //通过SparkEnv 获取 ShuffleManager Reader 来读取上一个Stage Shuffle Writer 的结果,并封装成一个迭代器返回
    SparkEnv.get.shuffleManager.getReader(
      dep.shuffleHandle, split.index, split.index + 1, context, metrics)
      .read()
      .asInstanceOf[Iterator[(K, C)]]
  }

HadoopRDD

Scala 复制代码
  override def compute(theSplit: Partition, context: TaskContext): InterruptibleIterator[(K, V)] = {
    //制作一个 Iterator 并返回 
    val iter = new NextIterator[(K, V)] {

      //将Spark 的 Partition 转换成 Hadoop 的 Partition
      private val split = theSplit.asInstanceOf[HadoopPartition]
      logInfo("Input split: " + split.inputSplit)
      private val jobConf = getJobConf()

      private val inputMetrics = context.taskMetrics().inputMetrics
      private val existingBytesRead = inputMetrics.bytesRead

      // 为文件的block 信息 设置 InputFileBlockHolder
      split.inputSplit.value match {
        case fs: FileSplit =>
          InputFileBlockHolder.set(fs.getPath.toString, fs.getStart, fs.getLength)
        case _ =>
          InputFileBlockHolder.unset()
      }

      // 找到一个函数,该函数将返回此线程读取的FileSystem字节。在创建RecordReader之前执行此操作,因为RecordReader的构造函数可能会读取一些字节
      private val getBytesReadCallback: Option[() => Long] = split.inputSplit.value match {
        case _: FileSplit | _: CombineFileSplit =>
          Some(SparkHadoopUtil.get.getFSBytesReadOnThreadCallback())
        case _ => None
      }

      // 我们从线程本地Hadoop FileSystem统计数据中获取输入字节。
      //然而,如果我们进行合并,我们可能会在同一任务和同一线程中计算多个分区,
      //在这种情况下,我们需要避免覆盖先前分区写入的值(SPARK-13071)。
      private def updateBytesRead(): Unit = {
        getBytesReadCallback.foreach { getBytesRead =>
          inputMetrics.setBytesRead(existingBytesRead + getBytesRead())
        }
      }

      private var reader: RecordReader[K, V] = null
      private val inputFormat = getInputFormat(jobConf)
      HadoopRDD.addLocalConfiguration(
        new SimpleDateFormat("yyyyMMddHHmmss", Locale.US).format(createTime),
        context.stageId, theSplit.index, context.attemptNumber, jobConf)

      reader =
        try {
          inputFormat.getRecordReader(split.inputSplit.value, jobConf, Reporter.NULL)
        } catch {
          ......
        }
      // 注册任务完成回调以关闭输入流。
      context.addTaskCompletionListener[Unit] { context =>
        // 更新关闭前读取的字节数是为了确保正确添加此线程中的延迟字节读取统计信息
        updateBytesRead()
        closeIfNeeded()
      }

      private val key: K = if (reader == null) null.asInstanceOf[K] else reader.createKey()
      private val value: V = if (reader == null) null.asInstanceOf[V] else reader.createValue()

      override def getNext(): (K, V) = {
        try {
          finished = !reader.next(key, value)
        } catch {
          ...
        }
        if (!finished) {
          inputMetrics.incRecordsRead(1)
        }
        if (inputMetrics.recordsRead % SparkHadoopUtil.UPDATE_INPUT_METRICS_INTERVAL_RECORDS == 0) {
          updateBytesRead()
        }
        (key, value)
      }

      override def close(): Unit = {
        if (reader != null) {
          InputFileBlockHolder.unset()
          try {
            reader.close()
          } catch {
            ......
          } finally {
            reader = null
          }
          if (getBytesReadCallback.isDefined) {
            updateBytesRead()
          } else if (split.inputSplit.value.isInstanceOf[FileSplit] ||
                     split.inputSplit.value.isInstanceOf[CombineFileSplit]) {
            // 如果我们无法从FS统计数据中读取字节,请回退到分割大小,这可能是不准确的。
            try {
              inputMetrics.incBytesRead(split.inputSplit.value.getLength)
            } catch {
             ......
            }
          }
        }
      }
    }
    new InterruptibleIterator[(K, V)](context, iter)
  }

四、总结

1、 一个Stage的最后一个RDD会调用自己的iterator()

2、判断该RDD是否是被持久化的,如果是,直接从这个RDD的持久化开始迭代的计算

3、判断该RDD是否是Checkpoint,如果是,直接从这个RDD开始迭代的计算

4、如果2和3不满足,调用父RDD的iterator()

5、判断父RDD是否满足2和3

6、如果父RDD2和3不满足,调用父的父RDD的iterator()

7、直到该RDD是HadoopRDD或者ShuffledRDD ,也就是该Stage最初的那个RDD

8、从Hadoop文件系统(HDFS或者本地文件系统)或使用 Shuffle Reader 读上一个Stage的Shuffle Writer 结果 再依次调用每个算子的函数,返回结果集

9、使用Shuffle Writer 将结果写入磁盘,这个Stage的Task完成

相关推荐
PersistJiao1 小时前
在 Spark RDD 中,sortBy 和 top 算子的各自适用场景
大数据·spark·top·sortby
2301_811274311 小时前
大数据基于Spring Boot的化妆品推荐系统的设计与实现
大数据·spring boot·后端
Yz98761 小时前
hive的存储格式
大数据·数据库·数据仓库·hive·hadoop·数据库开发
青云交1 小时前
大数据新视界 -- 大数据大厂之 Hive 数据导入:多源数据集成的策略与实战(上)(3/ 30)
大数据·数据清洗·电商数据·数据整合·hive 数据导入·多源数据·影视娱乐数据
lzhlizihang1 小时前
python如何使用spark操作hive
hive·python·spark
武子康1 小时前
大数据-230 离线数仓 - ODS层的构建 Hive处理 UDF 与 SerDe 处理 与 当前总结
java·大数据·数据仓库·hive·hadoop·sql·hdfs
武子康1 小时前
大数据-231 离线数仓 - DWS 层、ADS 层的创建 Hive 执行脚本
java·大数据·数据仓库·hive·hadoop·mysql
时差9532 小时前
Flink Standalone集群模式安装部署
大数据·分布式·flink·部署
锵锵锵锵~蒋2 小时前
实时数据开发 | 怎么通俗理解Flink容错机制,提到的checkpoint、barrier、Savepoint、sink都是什么
大数据·数据仓库·flink·实时数据开发
二进制_博客2 小时前
Flink学习连载文章4-flink中的各种转换操作
大数据·学习·flink