Spark基础篇(二) - RDD的理论知识

一、什么是RDD

二、RDD的源码定义

三、RDD五大特性详解

四、RDD五大特性和源码的对应关系

五、图解RDD

一、什么是RDD

在Spark中,建议大家面向DF/DS编程,但是不管怎么滴,RDD的理解对于后续使用高级别的API使用会更好。

RDD:Resilent Distributed Dataset,弹性分布式数据集,是Spark中最基本的数据抽象(the basic abstraction in spark)
作用:让开发者大大降低开发分布式应用程序的门槛及提高执行效率。

直接GitHub上查看RDD的源码:
https://github.com/apache/spark/blob/master/core/src/main/scala/org/apache/spark/rdd/RDD.scala

A Resilient Distributed Dataset (RDD), the basic abstraction in Spark. Represents an immutable,

2、RDD中的元素可以被分区成很多部分,能够被并行的操作。
 * partitioned collection of elements that can be operated on in parallel. This class contains the
 * basic operations available on all RDDs, such as `map`, `filter`, and `persist`. In addition,
 * [[org.apache.spark.rdd.PairRDDFunctions]] contains operations available only on RDDs of key-value
 * pairs, such as `groupByKey` and `join`;
 * [[org.apache.spark.rdd.DoubleRDDFunctions]] contains operations available only on RDDs of
 * Doubles; and
 * [[org.apache.spark.rdd.SequenceFileRDDFunctions]] contains operations available on RDDs that
 * can be saved as SequenceFiles.
 * All operations are automatically available on any RDD of the right type (e.g. RDD[(Int, Int)])
 * through implicit.

===>上述源码剖析:
1、Distributed Dataset:分布式数据集,数据归属在大的机器上,以分区的形式存储;
2、Resilent(弹性)–>弹性表现在计算之上;比如某个节点挂了,某个部分的数据丢失了,能够通过某些机制进行修复,这个就提现了它的弹性;
3、Distributed也意味着数据可以存储在不同的节点之上;
4、Dataset(数据集):可以理解为HDFS中的block,一个文件就是一个Dataset、

  • Represents an immutable,代表了一个不可变的,–》val
    意味着我们的RDD一旦产生就是不可变的,RDDA --> RDDB;RDDB必然是一个新的

  • Partitioned collection of elements ,RDD中的元素能够被分区,理解为HDFS中的block,Mapreduce中的Split

  • that can be operated on in parallel, RDD中的元素能够以一种并行的方式被操作,假设RDD中的元素有三个分区:一个RDD元素被拆分成了几块,每一块都在不同的节点上存储:举例:
    RDDA:
    Hadoop001:Partition1(1、2、3)
    Hadoop002:Partition2(4、5、6)
    Hadoop003:Partition3(7、8、9)

假设我们对这个RDDA进行了一个+1的操作,operated+1,三个分区中分别存放在三台机器上,operated + 1:是同时对这三个分区进行操作的。

输出:
Hadoop001:Partition1(2、3、4)
Hadoop002:Partition2(5、6、7)
Hadoop003:Partition3(8、9、10)

二、RDD的源码定义

abstract class RDD[T: ClassTag](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]]
  ) extends Serializable with Logging {

解析:
1、抽象类RDD不能直接new,必然是有子类实现的,我们使用时直接使用其子类即可,如下是jdbcRDD,都是继承自RDD类

class JdbcRDD[T: ClassTag](
    sc: SparkContext,
    getConnection: () => Connection,
    sql: String,
    lowerBound: Long,
    upperBound: Long,
    numPartitions: Int,
    mapRow: (ResultSet) => T = JdbcRDD.resultSetToObjectArray _)
  extends RDD[T](sc, Nil) with Logging {

2、序列化Serializable 网络的传输
3、logging 记录日志的,在spark1.6中是能直接使用的,在spark2.0以后被移除了。
4、T 泛型,支持字符串,自定义类型
5、sparkContext
6、@transient

三、RDD五大特性详解

  • 五大特性在源码中有所描述:
 Internally, each RDD is characterized by five main properties:
 *
 *  - 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)

1、A list of partitions

  • RDD是由一系列的分区所构成的

2、A function for computing each split

  • 举例:+1这个操作就是对所有分区进行的

3、A list of dependencies on other RDDs

  • RDDA --> RDDB --> RDDC --> RDDD,每一个分区间都有依赖关系

4、Optionally, a Partitioner for key-value RDDs(eg. to say that the RDD is hash-partiitoned)

  • 在Spark中默认是一个hash还有一个range的分区

5、Optionnally,a list of preferred locations to compute each split on (bolck location for HDFS file)

  • 数据本地化的概念,计算的数据在哪,就把task分配到数据所在的地方,因为对于hdfs来说是有多个副本的

–>RDD的这5大特性和Resilent Distributed Dataset之间的关系,如何与之对应。

小结:

在信息爆炸的今天,即使单台机器的配置再怎么高,也是应付不了大规模的数据处理的;在数据存储的时候选择分布式的,在计算的时候也要选择分布式,在数据存储的时候要进行分割,并且以多副本的方式进行保存,此举以便于容错。计算的时候也要进行切分,以一个task去处理2个block。传统的分布式存储(HDFS)+分布式计算(MapReduce)

四、RDD五大特性和源码的对应关系

1、def compute(split: Partition, context: TaskContext): Iterator[T]

  • 对应RDD的第二个特性,做计算就是对RDD中的分区做计算;计算方法传入参数肯定有Partition,后面是

2、protected def getPartitions: Array[Partition]

  • 一个RDD是由一系列的分区构成,那它返回的必然是一个数组或集合,集合中存放类型必然是Partition ⇒ 对应RDD中的第一个特性 a list of partitions

3、protected def getDependencies: Seq[Dependency[_]] = deps

  • RDD中是有转换的,体现在依赖关系上,RDD --> RDDB --> RDDC

4、protected def getPreferredLocations(split: Partition): Seq[String] = Nil

  • 定义了getPreferredLocations方法,得到最优先的位置,返回的是一个数组或集合的东西;对应RDD中的数据本地化特点。

5、@transient val partitioner: Option[Partitioner] = None

  • 以键值对方式(key,value)的时候有一个partiitoner,—>对应第四大特点
打开JdbcRDD.scala
override def getPartitions: Array[Partition] = {
    // bounds are inclusive, hence the + 1 here and - 1 on end
    val length = BigInt(1) + upperBound - lowerBound
    (0 until numPartitions).map { i =>
      val start = lowerBound + ((i * length) / numPartitions)
      val end = lowerBound + (((i + 1) * length) / numPartitions) - 1
      new JdbcPartition(i, start.toLong, end.toLong)
    }.toArray
  }				//获取到分区信息,操作JDBC的方式,代码中有一个起始和结束位置,eg:有10个分区,10000调数据,每个分区处理数据都有对应顺序,比如第一个分区处理第1~1000条数据,第二个分区处理1001~2000的数据。
  每个分区做一个map,开始和结束构建出来一个JdbcParttion,就能得到partitions.

2、标准的Jdbc编程
 override def compute(thePart: Partition, context: TaskContext): Iterator[T] = new NextIterator[T]
  {
    context.addTaskCompletionListener[Unit]{ context => closeIfNeeded() }
    val part = thePart.asInstanceOf[JdbcPartition]
    val conn = getConnection()
    val stmt = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)

    val url = conn.getMetaData.getURL
    if (url.startsWith("jdbc:mysql:")) {
      // setFetchSize(Integer.MIN_VALUE) is a mysql driver specific way to force
      // streaming results, rather than pulling entire resultset into memory.
      // See the below URL
      // dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-implementation-notes.html

      stmt.setFetchSize(Integer.MIN_VALUE)
    } else {
      stmt.setFetchSize(100)
    }

    logInfo(s"statement fetch size set to: ${stmt.getFetchSize}")

    stmt.setLong(1, part.lower)
    stmt.setLong(2, part.upper)
    val rs = stmt.executeQuery()

    override def getNext(): T = {
      if (rs.next()) {
        mapRow(rs)
      } else {
        finished = true
        null.asInstanceOf[T]
      }
    }

    override def close() {
      try {
        if (null != rs) {
          rs.close()
        }
      } catch {
        case e: Exception => logWarning("Exception closing resultset", e)
      }
      try {
        if (null != stmt) {
          stmt.close()
        }
      } catch {
        case e: Exception => logWarning("Exception closing statement", e)
      }
      try {
        if (null != conn) {
          conn.close()
        }
        logInfo("closed connection")
      } catch {
        case e: Exception => logWarning("Exception closing connection", e)
      }
    }
  }
}

3、HadoopRDD.scala(读取hadoop文件的)
override def getPartitions: Array[Partition] = {
    val jobConf = getJobConf()
    // add the credentials here as this can be called before SparkContext initialized
    SparkHadoopUtil.get.addCredentials(jobConf)
    try {
      val allInputSplits = getInputFormat(jobConf).getSplits(jobConf, minPartitions)
      val inputSplits = if (ignoreEmptySplits) {
        allInputSplits.filter(_.getLength > 0)
      } else {
        allInputSplits
      }
      val array = new Array[Partition](inputSplits.size)
      for (i <- 0 until inputSplits.size) {
        array(i) = new HadoopPartition(id, i, inputSplits(i))
      }
      array

//读文件借助于inputformat,对于RDD来说,inputformat.getsplits,拿到一堆inputsplits,partition大小就是inputsplits大小,构建一个数组,每个数组传一个partition,数组大小就是inptsplits.size;迭代inputsplits,每个构建hadoopPartition。

五、图解RDD

  • 如下图:1个RDD中有5个分区,每个分区中有5个元素,这5个分区分别存储在不同的节点上(NODE1、NODE2、NODE3);数据极有可能存放在disk上也有可能存放在memory上,由于多副本策略,也有可能一个分区存多份;
  • 此时有5个分区,必然会启动5个task,如果core够的话,5个task必然是会并行执行的;如果core不够的话,会先跑完一轮再跑下一轮。

eg:partition的数据分布在各个节点上,如果在hadoop002上运行partition1(需要先通过网络把Partition的数据拷贝到hadoop002上才能做响应的计算,提现的是数据的本地性)
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值