Spark RDD

Spark RDD的简介

什么是RDD?

RDD是整个Spark的基石,是一个弹性分布式的数据集,为用户屏蔽了底层复杂的计算和映射操作。

RDD的特点:

  • RDD 是不可变的,如果对一个RDD进行转换操作会生成一个新的RDD。
  • RDD 是分区的,RDD 里面的具体数据是分布在多台机器上的 Excutor里面的。
  • RDD 是弹性的。

RDD 的弹性特征:

  • 弹性:Spark会根据用户的配置或者当前Spark的应用运行情况去自动将RDD的数据缓存到内存或者磁盘。它是一个对用户不可见的封装的功能。
  • 容错:当你的RDD数据被删除或者丢失的时候,可以通过血统或者检查点机制恢复数据,这个对用户来说也是透明的。
  • 计算:RDD的计算是分层的,有 应用->job->Stage->TaskSet->Task ,每一层都有相对应的计算的保障与重复机制。保障你的计算不会由于一些突然的因素而发生终止。
  • 分片:你可以根据业务需求或者一些算子来重新调整RDD的数据分布情况。

如何创建RDD

从外部文件创建

从外部文件创建RDD可以使用textFile()方法。

textFile()方法源码:

  /**
   * Read a text file from HDFS, a local file system (available on all nodes), or any
   * Hadoop-supported file system URI, and return it as an RDD of Strings.
   * @param path path to the text file on a supported file system
   * @param minPartitions suggested minimum number of partitions for the resulting RDD
   * @return RDD of lines of the text file
   */
  def textFile(
      path: String,
      minPartitions: Int = defaultMinPartitions): RDD[String] = withScope {
    assertNotStopped()
    hadoopFile(path, classOf[TextInputFormat], classOf[LongWritable], classOf[Text],
      minPartitions).map(pair => pair._2.toString).setName(path)
  }

textFile(path: String,minPartitions: Int = defaultMinPartitions)有两个参数:

  • 第一个参数是文件的路径

  • 第二个参数是构建RDD后的分区数量,可以跟你传入的参数将数据划分成多个分区。

如果你不指定分区的数量,首先会获取你的Task数量即defaultParallelism,然后再和2比较。如果你的Task数量大于或等于2,那么默认会将你的数据集分成两个区。如果你的Task数量是1,那么默认会将你的数据分成一个区。

defaultMinPartitions方法,比较你的Task数量和2的大小:

def defaultMinPartitions: Int = math.min(defaultParallelism, 2)

defaultParallelism方法,获取你的Task数量:

  /** Default level of parallelism to use when not given by user (e.g. parallelize and makeRDD). */
  def defaultParallelism: Int = {
    assertNotStopped()
    taskScheduler.defaultParallelism
  }

示例:

从本地外部文件创建RDD:

scala> sc.textFile("file:///usr/hdp/3.1.0.0-78/spark2/README.md")
res0: org.apache.spark.rdd.RDD[String] = file:///usr/hdp/3.1.0.0-78/spark2/README.md MapPartitionsRDD[1] at textFile at <console>:25

从hdfs文件系统中读取文件创建RDD:

scala> sc.textFile("hdfs:///README.md")
res2: org.apache.spark.rdd.RDD[String] = hdfs:///README.md MapPartitionsRDD[3] at textFile at <console>:25

读取本地文件时,需要在文件路径前面加上file://。从hdfs上读取文件的时候,需要在文件路径上加上hdfs://。如果你不在文件路径前面加上文件系统协议,会默认到hdfs文件系统中读取文件。

从scala变量转换成RDD

从scala变量转换成RDD有两个方法,一个是parallelize(),另一个是makeRDD()

parallelize()

parallelize() 源码:

def parallelize[T: ClassTag](
      seq: Seq[T],
      numSlices: Int = defaultParallelism): RDD[T] = withScope {
    assertNotStopped()
    new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]())
  }

parallelize[T: ClassTag](seq: Seq[T],numSlices: Int = defaultParallelism)同样有两个参数:

  • 第一个参数是scala的变量。这个变量是可以是Seq下面的所有子类。

  • 第二个参数是指定创建后RDD的分区数量,如果不指定也是默认在Task数量和2中选一个最小的值作为分区数量。

不指定分区示例:

//从Seq创建RDD
scala> sc.parallelize(Seq(1 to 5))
res3: org.apache.spark.rdd.RDD[scala.collection.immutable.Range.Inclusive] = ParallelCollectionRDD[4] at parallelize at <console>:25

scala> res3.collect
res4: Array[scala.collection.immutable.Range.Inclusive] = Array(Range(1, 2, 3, 4, 5))

//从Array创建RDD
scala> sc.parallelize(Array(1 to 5))
res5: org.apache.spark.rdd.RDD[scala.collection.immutable.Range.Inclusive] = ParallelCollectionRDD[5] at parallelize at <console>:25

scala> res5.collect
res6: Array[scala.collection.immutable.Range.Inclusive] = Array(Range(1, 2, 3, 4, 5))

//从List创建RDD
scala> sc.parallelize(List(1 to 5))
res7: org.apache.spark.rdd.RDD[scala.collection.immutable.Range.Inclusive] = ParallelCollectionRDD[6] at parallelize at <console>:25

scala> res7.collect
res8: Array[scala.collection.immutable.Range.Inclusive] = Array(Range(1, 2, 3, 4, 5))

指定分区示例:

//我的Task是1,所以默认的分区数量是1        
scala> sc.parallelize(Seq(1 to 5))
res9: org.apache.spark.rdd.RDD[scala.collection.immutable.Range.Inclusive] = ParallelCollectionRDD[7] at parallelize at <console>:25
                                                                                                       
scala> res9.partitions.size
res10: Int = 1

//指定数据存放到10个分区
scala> sc.parallelize(Seq(1 to 5),10)
res11: org.apache.spark.rdd.RDD[scala.collection.immutable.Range.Inclusive] = ParallelCollectionRDD[8] at parallelize at <console>:25

scala> res11.partitions.size
res12: Int = 10

makeRDD()

makeRDD()有两个重载的方法:

第一个重载方法源码:

  /** Distribute a local Scala collection to form an RDD.
   *
   * This method is identical to `parallelize`.
   * @param seq Scala collection to distribute
   * @param numSlices number of partitions to divide the collection into
   * @return RDD representing distributed collection
   */
  def makeRDD[T: ClassTag](
      seq: Seq[T],
      numSlices: Int = defaultParallelism): RDD[T] = withScope {
    parallelize(seq, numSlices)
  }

第一个重载方法 makeRDD[T: ClassTag](seq: Seq[T],numSlices: Int = defaultParallelism)

实际和parallelize()方法参数一样,在方法体里面实际上也是调用parallelize(),这里就不展开讲了。

第二个重载方法源码:

  /**
   * Distribute a local Scala collection to form an RDD, with one or more
   * location preferences (hostnames of Spark nodes) for each object.
   * Create a new partition for each collection item.
   * @param seq list of tuples of data and location preferences (hostnames of Spark nodes)
   * @return RDD representing data partitioned according to location preferences
   */
  def makeRDD[T: ClassTag](seq: Seq[(T, Seq[String])]): RDD[T] = withScope {
    assertNotStopped()
    val indexToPrefs = seq.zipWithIndex.map(t => (t._2, t._1._2)).toMap
    new ParallelCollectionRDD[T](this, seq.map(_._1), math.max(seq.size, 1), indexToPrefs)
  }

第二个重载方法makeRDD[T: ClassTag](seq: Seq[(T, Seq[String])])

可以指定RDD的数据存放在哪个分区上,传入的参数是一个Seq。

Seq里面的每个一元素都是一个元组,元组的第一个元素就是分区号,第二个元素是每个分区里面的数据。

示例:

//创建3个分区的数据
scala> val mrdd=sc.makeRDD(List((1,List("a","b","c")),(2,List("b","c","d")),(3,List("c","d","e"))))
mrdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[1] at makeRDD at <console>:24

//查看分区的数量
scala> mrdd.partitions.size
res1: Int = 3

//获取第一个分区的数据
scala> mrdd.preferredLocations(mrdd.partitions(0))
res2: Seq[String] = List(a, b, c)

从其他RDD转换

RDD的转换操作到时候会单独写一篇

posted @ 2020-08-28 11:43  JunCode  阅读(309)  评论(0编辑  收藏  举报