前言

  用Spark有一段时间了,但是感觉还是停留在表面,对于Spark的RDD的理解还是停留在概念上,即只知道它是个弹性分布式数据集,其他的一概不知

有点略显惭愧。下面记录下我对RDD的新的理解。

官方介绍

   弹性分布式数据集。 RDD是只读的、分区记录的集合。RDD只能基于在稳定物理存储中的数据集和其他已有的RDD上执行确定性操作来创建。

问题

只要你敢问度娘RDD是什么,包你看到一大片一模一样的答案,都是说这样的概念性的东西,没有任何的价值。

我只想知道 RDD为什么是弹性 而不是 不弹性, RDD到底是怎么存数据,在执行任务的过程中是咋哪个阶段读取数据。

什么是弹性

我的理解如下(若有误或不足,烦请指出更正):

1. RDD可以在内存和磁盘之间手动或自动切换

2. RDD可以通过转换成其他的RDD,即血统

3. RDD可以存储任意类型的数据

存储的内容是什么

根据编写Spark任务的代码来看,很直观的感觉是RDD就是一个只读的数据,例如  rdd.foreach(println)

但是不是, RDD其实不存储真是的数据,只存储数据的获取的方法,以及分区的方法,还有就是数据的类型。

百闻不如一见, 下面看看RDD的源码:

//其他的代码删除了,主要保留了它的两个抽象方法
abstract class RDD[T: ClassTag](
@transient private var _sc: SparkContext,
@transient private var deps: Seq[Dependency[_]]
) extends Serializable with Logging {

//计算某个分区数据的方法 ,将某个分区的数据读成一个 Iterator
def compute(split: Partition, context: TaskContext): Iterator[T]
  //计算分区信息 只会被调用一次
protected def getPartitions: Array[Partition] }

  通过RDD的这两个抽象方法,我们可以看出 :

RDD其实是不存储真是数据的,存储的的只是 真实数据的分区信息getPartitions,还有就是针对单个分区的读取方法 compute

到这里可能就有点疑惑,要是RDD只存储这分区信息和读取方法,那么RDD的依赖信息是怎么保存的?

其实RDD是有保存的,只是我粘贴出的只是RDD顶层抽象类,还要一点需要注意 ,RDD只能向上依赖,而真正实现这两个方法的RDD都是整个任务的输入端,即处于RDD血统的顶层,初代RDD

举个例子:val rdd = sc.textFile(...); val rdd1 = rdd.map(f)  .  这里的 rdd是初代RDD, 是没有任何依赖的RDD的,所以没就没有保存依赖信息, 而 rdd1是子代RDD,那么它就必须得记录下自己是来源于谁,也就是血统,

下面展示的是HadoopRDD和  MapPartitionsRDD

//负责记录数据的分区信息  和 读取方法

class HadoopRDD[K, V](
  @transient sc: SparkContext,
  broadcastedConf: Broadcast[SerializableConfiguration],
  initLocalJobConfFuncOpt: Option[JobConf => Unit],
  inputFormatClass: Class[_ <: InputFormat[K, V]],
  keyClass: Class[K],
  valueClass: Class[V],
  minPartitions: Int)
  extends RDD[(K, V)](sc, Nil) with Logging {

override def getPartitions: Array[Partition] = { ***篇幅所限  自己查看**}

override def compute(theSplit: Partition, context: TaskContext): InterruptibleIterator[(K, V)] = {***篇幅所限  自己查看**}

}

//子代RDD的作用起始很简单  就是记录初代RDD到底在干了什么才得到了自己

private[spark] class MapPartitionsRDD[U: ClassTag, T: ClassTag](

    prev: RDD[T],  //上一代RDD
f: (TaskContext, Int, Iterator[T]) => Iterator[U], // (TaskContext, partition index, iterator) //初代RDD生成自己的方法
preservesPartitioning: Boolean = false)
extends RDD[U](prev) { override val partitioner = if (preservesPartitioning) firstParent[T].partitioner else None override def getPartitions: Array[Partition] = firstParent[T].partitions override def compute(split: Partition, context: TaskContext): Iterator[U] =
f(context, split.index, firstParent[T].iterator(split, context))
}

  到这里,我们就大概了解了RDD到底存储了什么东西,

初代RDD: 处于血统的顶层,存储的是任务所需的数据的分区信息,还有单个分区数据读取的方法,没有依赖的RDD, 因为它就是依赖的开始。

子代RDD: 处于血统的下层, 存储的东西就是 初代RDD到底干了什么才会产生自己,还有就是初代RDD的引用

现在我们基本了解了RDD里面到底存储了些什么东西,那么问题就来了,到底读取数据发生在什么时候。

数据读取发生在什么时候

直接开门见山的说, 数据读取是发生在运行的Task中,也就是说,数据是在任务分发的executor上运行的时候读取的,上源码:

private[spark] class ResultTask[T, U](
stageId: Int,
stageAttemptId: Int,
taskBinary: Broadcast[Array[Byte]],
partition: Partition,
@transient locs: Seq[TaskLocation],
val outputId: Int,
internalAccumulators: Seq[Accumulator[Long]])
extends Task[U](stageId, stageAttemptId, partition.index, internalAccumulators)
with Serializable { @transient private[this] val preferredLocs: Seq[TaskLocation] = {
if (locs == null) Nil else locs.toSet.toSeq
} override def runTask(context: TaskContext): U = {
// Deserialize the RDD and the func using the broadcast variables.
val deserializeStartTime = System.currentTimeMillis()
val ser = SparkEnv.get.closureSerializer.newInstance()
val (rdd, func) = ser.deserialize[(RDD[T], (TaskContext, Iterator[T]) => U)](
ByteBuffer.wrap(taskBinary.value), Thread.currentThread.getContextClassLoader)
_executorDeserializeTime = System.currentTimeMillis() - deserializeStartTime metrics = Some(context.taskMetrics)
func(context, rdd.iterator(partition, context)) //这里调用了 rdd.iterator , 下面看看RDD的这个方法
} // This is only callable on the driver side.
override def preferredLocations: Seq[TaskLocation] = preferredLocs override def toString: String = "ResultTask(" + stageId + ", " + partitionId + ")"
}

final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
  if (storageLevel != StorageLevel.NONE) {

//先判断是否有缓存 ,有则直接从缓存中取 , 没有就从磁盘中取出来, 然后再执行缓存操作 
    SparkEnv.get.cacheManager.getOrCompute(this, split, context, storageLevel) 
  } else {

//直接从磁盘中读取 或 从 检查点中读取 
    computeOrReadCheckpoint(split, context)
  }
}

  在spark中的任务 最终是会被分解成多个TaskSet到executor上运行,TaskSet的划分是根据是否需要shuffle来的。

在spark中就只有两种Task,一种是ResultTask ,一种是ShuffleTask, 两种Task都是以相同的方式读取RDD的数据。

Spark RDD到底是个什么东西的更多相关文章

  1. 【spark 深入学习 03】Spark RDD的蛮荒世界

    RDD真的是一个很晦涩的词汇,他就是伯克利大学的博士们在论文中提出的一个概念,很抽象,很难懂:但是这是spark的核心概念,因此有必要spark rdd的知识点,用最简单.浅显易懂的词汇描述.不想用学 ...

  2. spark RDD 常见操作

    fold 操作 区别 与 co 1.mapValus 2.flatMapValues 3.comineByKey 4.foldByKey 5.reduceByKey 6.groupByKey 7.so ...

  3. 关于Spark RDD 的认识

    一.基本认识 RDD 是Spark大数据计算引擎中,抽象的一种数据结构. RDD(Resilient Distributed Dataset),中文意思是弹性分布式数据集,它是Spark中的基本抽象. ...

  4. 通过WordCount解析Spark RDD内部源码机制

    一.Spark WordCount动手实践 我们通过Spark WordCount动手实践,编写单词计数代码:在wordcount.scala的基础上,从数据流动的视角深入分析Spark RDD的数据 ...

  5. Spark Rdd coalesce()方法和repartition()方法

    在Spark的Rdd中,Rdd是分区的. 有时候需要重新设置Rdd的分区数量,比如Rdd的分区中,Rdd分区比较多,但是每个Rdd的数据量比较小,需要设置一个比较合理的分区.或者需要把Rdd的分区数量 ...

  6. Spark RDD API详解(一) Map和Reduce

    RDD是什么? RDD是Spark中的抽象数据结构类型,任何数据在Spark中都被表示为RDD.从编程的角度来看,RDD可以简单看成是一个数组.和普通数组的区别是,RDD中的数据是分区存储的,这样不同 ...

  7. Spark RDD aggregateByKey

    aggregateByKey 这个RDD有点繁琐,整理一下使用示例,供参考 直接上代码 import org.apache.spark.rdd.RDD import org.apache.spark. ...

  8. Spark RDD解密

    1.  基于数据集的处理: 从物理存储上加载数据,然后操作数据,然后写入数据到物理设备; 基于数据集的操作不适应的场景: 不适合于大量的迭代: 不适合交互式查询:每次查询都需要对磁盘进行交互. 基于数 ...

  9. Spark - RDD(弹性分布式数据集)

    org.apache.spark.rddRDDabstract class RDD[T] extends Serializable with Logging A Resilient Distribut ...

随机推荐

  1. C#函数式编程之可选值

    在我们的实际开发中已经会遇到可空类型,而在C#中自从2.0之后就提供了可空类型(Nullable<T>),普通的值类型是不可以赋值为NULL,但是在类型的后面加上问号就变成了可空类型,这样 ...

  2. vc编译 curl 7.36.0

    CURL邮件列表中提到官方最新版本的windows devel包中缺少文件,而我又用不到https,所以我就自己下载源码包来编译了 下载源码包:http://curl.haxx.se/download ...

  3. C语言结构体-struct

    知识点: 1)结构体的定义. 2)结构体的sizeof. 3)  结构体的指针. 1) 结构体的定义: 在逻辑上有一定关联的多个数据类型做为一整体进行操作的数据结构,它的关键字是struct.下面我将 ...

  4. 我的ORM之三 -- 更新

    我的ORM索引 更新语法 var 影响行数 = dbr.表.Update(实体).Where(条件).Execute(); 实体类型: 更新的实体类型和添加的实体类型一样,有三类: 1. 任何C#类. ...

  5. Java虚拟机5:Java垃圾回收(GC)机制详解

    哪些内存需要回收? 哪些内存需要回收是垃圾回收机制第一个要考虑的问题,所谓“要回收的垃圾”无非就是那些不可能再被任何途径使用的对象.那么如何找到这些对象? 1.引用计数法 这个算法的实现是,给对象中添 ...

  6. 【C语言学习】《C Primer Plus》第2章 C语言的概述

    学习总结 1.#include是C预处理命令之一,#include指向的是头文件,如#include <stdio.h>,这个stdio.h就是程序需要引用的C标准库之一.里面包含了pri ...

  7. 团队项目——站立会议DAY7

    第七次站立会议记录: 参会人员:张靖颜,钟灵毓秀,何玥,赵莹,王梓萱 项目进展: 1.张靖颜:对功能模块代码进行近一步的审查和辅助,并对出错处进行修改和完善. 2.钟灵毓秀:对代码近一步的修改,将各个 ...

  8. Senparc.Weixin.MP SDK 微信公众平台开发教程(十四):请求消息去重

    为了确保信息请求消息的到达率,微信服务器在没有及时收到响应消息(ResponseMessage)的情况下,会多次发送同一条请求消息(RequestMessage),包括MsgId等在内的所有文本内容都 ...

  9. 暂停JS的深入学习

    对于JS的掌握程度,已经满足中级前端工程师的需要了,现在需要入门一下后端的基础知识,如nginx服务器一类的知识,方便今后快速开发. 先来nginx,文章.

  10. xamarin UWP设置HUD加载功能

    使用xamarin开发的时候经常用到加载HUD功能,就是我们常见的一个加载中的动作,Android 下使用 AndHUD , iOS 下使用 BTProgressHUD, 这两个在在 NuGet 上都 ...