spark封神之路(6)-RDD创建详解

3.2 RDD编程

在 Spark 中,RDD 被表示为对象,通过对象上的方法调用来对 RDD 进行转换。经过一系列的transformations定义 RDD 之后,就可以调用 actions 触发 RDD 的计算action可以是向应用程序返回结果(count, collect等),或者是向存储系统保存数据(saveAsTextFile等)。在Spark中,只有遇到action,才会执行 RDD 的计算(即延迟计算),这样在运行时可以通过管道的方式传输多个转换(计算逻辑)。要使用 Spark,开发者需要编写一个 Driver 程序,它被提交到集群以调度运行 Worker,Driver 中定义了一个或多个 RDD,并调用 RDD 上的 action,Worker 则执行 RDD 分区计算任务。

3.2.1 RDD的创建方式

  1. 本地集合转换 RDD ---算子--> RDD

  2. 加载外部数据

3.2.1.1 由集合转换

/**
 * Author:   Hang.Z
 * Description: 
 * 说明:
 * •一旦 RDD 创建成功, 就可以通过并行的方式去操作这个分布式的数据集了.
 * •parallelize和makeRDD还有一个重要的参数就是把数据集切分成的分区数.
 * •Spark 会为每个分区运行一个任务(task). 正常情况下, Spark 会自动的根据你的集群来设置分区数
 */
object CreateRDD {
  def main(args: Array[String]): Unit = {
    //创建spark环境
    val conf = new SparkConf()
    conf.setAppName(CreateRDD.getClass.getSimpleName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    //调用函数 创建RDD
    val rdd1: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7))
    //也可以调用创建RDD 
    val rdd2: RDD[String] = sc.parallelize(Seq[String]("scala", "java", "c++", "SQL"))
    // 将数据收集到本地 打印
    rdd1.collect().foreach(println)
    // 释放资源
    sc.stop()
  }

示例

package com._51doit.spark.day02

import org.apache.log4j.{Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
 * Author:   Hang.Z
 * Date:     21/06/02 
 * Description:
 * 本地集合生成RDD
 *  1 RDD的分区个数  默认是所有的可用核数为分区数  local[6]
 *  2 分区数据的控制
 *     1)  local[N]  N <=  核数
 *     2) parallelize(seq , num)  num > 0   最好num不要大于核数
 *     [一个核处理一个Task ,有6个核  7个分区  产生7个Task  有一个Task阻塞
 *                                4个分区   产生4个Task  造成资源浪费
 *        并发7   并行度是6
 *     ]
 */
object _03MakeRDD {
  // 设置日志级别
  Logger.getLogger("org").setLevel(Level.ERROR)
  def main(args: Array[String]): Unit = {
    // this.getClass.getSimpleName 类名
    // 环境
    val conf: SparkConf = new SparkConf()
      .setMaster("local[6]")  // 使用本机器的所有核数 16
      .setAppName(this.getClass.getSimpleName)
    val sc = new SparkContext(conf)
    val arr = Array(1,2,3,4)
    val mp = Map[String,Int](("wb",23),("duanlang",34))
    // 创建RDD
    val rdd1: RDD[Int] = sc.parallelize(arr,4)
    // 创建RDD
    val rdd2: RDD[(String, Int)] = sc.parallelize(mp.toList,7)
    val size1: Int = rdd1.partitions.size
    val size2: Int = rdd2.partitions.size

    /**
     * 因为我的机器是16Core ,本地集合的RDD 默认就是机器的所有的可用核数为分区数
     */
    println(size1)// 16分区-->16Task
    println(size2)// 16分区


    sc.stop()

  }

}
package com._51doit.spark.day02

import org.apache.log4j.{Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
 * Author:   Hang.Z
 * Date:     21/06/02 
 * Description:
 * 本地集合生成RDD
 *  1 RDD的分区个数  默认是所有的可用核数为分区数  local[6]
 *
 */
object _02MakeRDD {
  // 设置日志级别
  Logger.getLogger("org").setLevel(Level.ERROR)
  def main(args: Array[String]): Unit = {
    // this.getClass.getSimpleName 类名
    // 环境
    val conf: SparkConf = new SparkConf()
      .setMaster("local[6]")  // 使用本机器的所有核数 16
      .setAppName(this.getClass.getSimpleName)
    val sc = new SparkContext(conf)
    val arr = Array(1,2,3,4)
    val mp = Map[String,Int](("wb",23),("duanlang",34))
    // 创建RDD
    val rdd1: RDD[Int] = sc.parallelize(arr)
    // 创建RDD
    val rdd2: RDD[(String, Int)] = sc.parallelize(mp.toList)
    val size1: Int = rdd1.partitions.size
    val size2: Int = rdd2.partitions.size

    /**
     * 因为我的机器是16Core ,本地集合的RDD 默认就是机器的所有的可用核数为分区数
     */
    println(size1)// 16分区-->16Task
    println(size2)// 16分区


    sc.stop()

  }

}
package com._51doit.spark.day02

import org.apache.log4j.{Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
 * Author:   Hang.Z
 * Date:     21/06/02 
 * Description:
 * 本地集合生成RDD
 *  1 RDD的分区个数  默认是所有的可用核数为分区数  local[6]
 *  2 分区数据的控制
 *     1)  local[N]  N <=  核数
 *     2) parallelize(seq , num)  num > 0   最好num不要大于核数
 *     [一个核处理一个Task ,有6个核  7个分区  产生7个Task  有一个Task阻塞
 *                                4个分区   产生4个Task  造成资源浪费
 *        并发7   并行度是6
 *     ]
 */
object _03MakeRDD {
  // 设置日志级别
  Logger.getLogger("org").setLevel(Level.ERROR)
  def main(args: Array[String]): Unit = {
    // this.getClass.getSimpleName 类名
    // 环境
    val conf: SparkConf = new SparkConf()
      .setMaster("local[6]")  // 使用本机器的所有核数 16
      .setAppName(this.getClass.getSimpleName)
    val sc = new SparkContext(conf)
    val arr = Array(1,2,3,4)
    val mp = Map[String,Int](("wb",23),("duanlang",34))
    // 创建RDD
    val rdd1: RDD[Int] = sc.parallelize(arr,4)
    // 创建RDD
    val rdd2: RDD[(String, Int)] = sc.parallelize(mp.toList,7)
    val size1: Int = rdd1.partitions.size
    val size2: Int = rdd2.partitions.size

    /**
     * 因为我的机器是16Core ,本地集合的RDD 默认就是机器的所有的可用核数为分区数
     */
    println(size1)// 16分区-->16Task
    println(size2)// 16分区
    sc.stop()

  }
}

总结:

本地集合创建RDD 默认有分区

1 分区个数 默认是可用的核数

2 修改分区 设置可用核数 local[*]

3 在创建RDD的时候执行分区个数

注意: 创建的分区的个数和节点的可用核数对应

扫描二维码关注公众号,回复: 13161455 查看本文章
// 设置日志级别
  Logger.getLogger("org").setLevel(Level.ERROR)
  def main(args: Array[String]): Unit = {
    // this.getClass.getSimpleName 类名
    // 环境
    val conf: SparkConf = new SparkConf()
      .setMaster("local[6]")  // 使用本机器的所有核数 16
      .setAppName(this.getClass.getSimpleName)
    val sc = new SparkContext(conf)
    val arr = Array(1,2,3,4)
    val mp = Map[String,Int](("wb",23),("duanlang",34))
    val rdd1: RDD[Int] = sc.makeRDD(arr, 4)
    println(rdd1.partitions.size)
    sc.stop()
  }

3.2.1.2 读取外部文件

可以是本地文件系统, HDFS, Cassandra, HVase, Amazon S3 等等.

Spark 支持 文本文件, SequenceFiles, 和其他所有的 Hadoop InputFormat

object CreatRDD_File {
  //设置控制台日志级别
  Logger.getLogger("org").setLevel(Level.ERROR)
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtil.getSc
    // 1 相对路劲读取本项目下的文件创建RDD
    val rdd1: RDD[String] = sc.textFile("spark-core/data/a.txt")
    // 2 绝对路径读取本地磁盘上的文件创建RDD
    val rdd2: RDD[String] = sc.textFile("D://word.txt")
    // 3 读取HDFS分布式文件系统中的文件创建RDD
    val rdd3: RDD[String] = sc.textFile("hdfs://doit01:8020/word.txt")
    // 4 使用 统配符加载目录下指定规则文件创建RDD
    val rdd4: RDD[String] = sc.textFile("spark-core/data/a*.txt")
    // 5  加载文件的时候获取文件名 创建RDD    返回(文件名 , 内容)的元组
    val rdd5: RDD[(String, String)] = sc.wholeTextFiles("spark-core/data/")
    // 收集数据 并打印
    rdd5.collect().foreach(println)
    sc.stop()
  }
}

1 url可以是本地文件系统文件, hdfs://..., s3n://...等等

2 如果是使用的本地文件系统的路径, 则必须每个节点都要存在这个路径

3 所有基于文件的方法, 都支持目录, 压缩文件, 和通配符(***). 例如:

textFile("/my/directory"), textFile("/my/directory/*.txt"), and textFile("/my/directory/*.gz").

4 textFile还可以有第二个参数, 表示分区数. 默认情况下, 每个块对应一个分区.(对 HDFS 来说, 块大小默认是 128M). 可以传递一个大于块数的分区数, 但是不能传递一个比块数小的分区数.

3.2.1.3 其他RDD转换

 

val conf = new SparkConf()
    conf.setAppName("wc").setMaster("local")
    val sc = new SparkContext(conf)
    // 创建RDD
    val rdd1: RDD[String] = sc.textFile("d://word.txt")
    //转换 返回新的RDD 
    val rdd2: RDD[String] = rdd1.flatMap(_.split("\\s+"))
    //转换 返回新的RDD 
    val rdd3: RDD[(String, Iterable[String])] = rdd2.groupBy(word => word)
    //转换 返回新的RDD 
    val rdd4: RDD[(String, Int)] = rdd3.map(tp => {
      (tp._1, tp._2.size)
    })
sc.stop()

3.2.2 RDD分区

RDD在被创建的时候已经有了分区 , RDD的分区是RDD并行计算的基础 ,一般情况下一个分区域会被封装成一个Task任务,如果资源足够多的情况下,RDD的分区数就是RDD计算的并行度 . 如果分区多,但是资源核数不够的情况下,分区数是大于并行度的!

3.2.2.1 分区个数

1 使用集合创建的RDD 的分区数

def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtil.getSc
    // 创建RDD
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6))
    // 手动设置分区数
    // val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6) . 6)
    // 查看RDD的分区数  , 在不指定分区数的情况下,RDD使用默认分区数
    /**
     * 1 加载使用集合创建RDD local模式下默认的分区数是 当前环境的可用核数
     *   1) setMaster("local[*]") 本地的所有核数
     *   2) setMaster("local[8]")  分配的8个核数
     *   3) conf.set("spark.default.parallelism", 4) 参数设置
     */
    println(rdd.partitions.size)
    sc.stop()
  }

2 加载文件创建RDD

def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtil.getSc
    // 1 相对路劲读取本项目下的文件创建RDD
    val rdd1: RDD[String] = sc.textFile("spark-core/data/a.txt")
    // 2 读取HDFS分布式文件系统中的文件创建RDD  1K  70M  212M  4
    val rdd3: RDD[String] = sc.textFile("hdfs://doit01:8020/wc/input/")
    /**
     * 根据文件数据创建RDD分区数至少为2
     * SplitSize 的计算逻辑
     * -- FileInputFormat.getSplits(JobConf job, int numSplits)
     * -- val goalSize: Long = totalSize / (if (numSplits == 0)  { 1}
     * else  { numSplits}).toLong
     * -- this.computeSplitSize(goalSize, minSize, blockSize);
     * -- protected long computeSplitSize(long goalSize, long minSize, long blockSize) {
     * return Math.max(minSize, Math.min(goalSize, blockSize));(1,3)}
     */
    println(rdd3.partitions.size)
    sc.stop()
  }

3.2.2.2 分区数据划分

集合RDD

def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtil.getSc
    val ls = List(1,2,3,4,5)
    //创建RDD  指定分区为3
    val rdd: RDD[Int] = sc.makeRDD(ls, 3)
    //生成3个结果文件 [1]  [2,3]   [4,5]
    rdd.saveAsTextFile("data/output1")
    sc.stop()
  }
---------------------------------------源码解析--------------------------
1) val rdd: RDD[Int] = sc.makeRDD(ls, 3)
2) makeRDD(ls, 3)--> 
     def makeRDD[T: ClassTag](
      seq: Seq[T], // data
      numSlices: Int = defaultParallelism): RDD[T] = withScope {  //3
    parallelize(seq, numSlices) // (data,3)
  } 
3) parallelize(seq,numSlices) -->
         def parallelize[T: ClassTag](
      seq: Seq[T],
      numSlices: Int = defaultParallelism): RDD[T] = withScope {
    assertNotStopped()
    new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]()) // 看这里两个参数
  }
4)  new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]())-->
       override def getPartitions: Array[Partition] = {
    val slices = ParallelCollectionRDD.slice(data, numSlices).toArray   // 看这里 
    slices.indices.map(i => new ParallelCollectionPartition(id, i, slices(i))).toArray
  }
5)slice(data, numSlices)---> 
      case _ =>   // 看这里
        val array = seq.toArray // To prevent O(n^2) operations for List etc
        positions(array.length, numSlices).map { case (start, end) =>  // 查看方法
            array.slice(start, end).toSeq
        }.toSeq
6)   positions(array.length, numSlices)--->
      def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {   // 数据核心分配逻辑
      (0 until numSlices).iterator.map { i => [0,3)  length=seq.length=5
        val start = ((i * length) / numSlices).toInt
        val end = (((i + 1) * length) / numSlices).toInt
        (start, end)
      }
(0,1)
(1,3)
(3,5)
7) array.slice---->
override def slice(from : scala.Int, until : scala.Int) : scala.Array[T] = { /* compiled code */ }
  • 文件RDD

读取文件数据时,数据是按照Hadoop文件读取的规则进行切片分区,而切片规则和数据读取的规则不一样

加载文件的时候 , 计算任务切片! 任务切片的个数就是分区的个数 [分大小]g

class HadoopRDD[K, V](
    sc: SparkContext,
    broadcastedConf: Broadcast[SerializableConfiguration],
    initLocalJobConfFuncOpt: Option[JobConf => Unit],
    inputFormatClass: Class[_ <: InputFormat[K, V]],
    keyClass: Class[K],
    valueClass: Class[V],
    minPartitions: Int)

  def defaultMinPartitions: Int = math.min(defaultParallelism, 2)
defaultParallelism 默认是当前机器的所有核数
******************************
minPartitions = 2
******************************
val rdd: RDD[String] = sc.textFile("D:\\spark_data")
1  返回 hadoopFile  任务切片的代码就是hadoop中的任务切片
  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)
  }
2 TextInputFormat   
  public class TextInputFormat extends FileInputFormat<LongWritable, Text> 
3 FileInputFormat
4  public InputSplit[] getSplits(JobConf job, int numSplits) throws IOException { //  numSplits = 2
    谁调用了这个方法 传入参数 
5  long goalSize = totalSize / (long)(numSplits == 0 ? 1 : numSplits); 
    goalSize =  (273+5)/2  TOINT
6     long splitSize = this.computeSplitSize(goalSize, minSize, blockSize);    
7
      protected long computeSplitSize(long goalSize, long minSize, long blockSize) {
        return Math.max(minSize, Math.min(goalSize, blockSize));
    }
    
     long minSize = Math.max(job.getLong("mapreduce.input.fileinputformat.split.minsize", 1L), this.minSplitSize);

8 Math.max(1, Math.min(139, 128m));    
    splitSize = 139B
9  计算任务切片个数 
    for(bytesRemaining = length; (double)bytesRemaining / (double)splitSize > 1.1D; bytesRemaining -= splitSize) {
                        splitHosts = this.getSplitHostsAndCachedHosts(blkLocations, length - bytesRemaining, splitSize, clusterMap);
                        splits.add(this.makeSplit(path, length - bytesRemaining, splitSize, splitHosts[0], splitHosts[1]));
                    }    
    
 5B  ------------>  1
 273B  ---------->  2
 1+2   任务切片计算   

猜你喜欢

转载自blog.csdn.net/qq_37933018/article/details/117826193