Spark:RDD数据分区数量总结(并行化集合parallelize与外部数据集textFile)

目录

 

1、创建RDD

2、RDD分区有关操作2.1、查看分区方式

2.2、查看分区数

2.3、查看不同分区内的数据

2.4、重新分区

2.5、设置分区数

3、分区数

3.1、并行化集合

3.2、外部数据集textFile

4、新版API中FileInputFormat的分片


1、创建RDD

Spark提供了两种方式创建RDD:

    读取外部数据集,如SparkContext.textFile
    在驱动器程序中对一个集合进行并行化,如SparkContext.paralleize或makeRDD

2、RDD分区有关操作
2.1、查看分区方式

在scala中可以通过RDD的partitioner属性获取RDD的分区方式,会返回一个scala.Option对象,这是scala中用来存放可能存在对象的容器类,可调用这个Option对象的isDefined来先插是否有值,调用get来获取其中的值。若存在值将会是spark.Partitioner对象

以下为standalone模式,集群中有4个Worker节点

    scala> val rdd = sc.parallelize(List(1,2,3,4,5,6,7))
    rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at <console>:24
     
    scala> rdd.partitioner
    res0: Option[org.apache.spark.Partitioner] = None

2.2、查看分区数

    scala> rdd.partitions.size
    res2: Int = 4

2.3、查看不同分区内的数据

    rdd.glom.collect
    res27: Array[Array[Int]] = Array(Array(1), Array(2, 3), Array(4, 5), Array(6, 7))

2.4、重新分区

重新分区是代价相对比较大的操作,因为它会把数据通过网络进行混洗,并创建出新的RDD

reparation函数可以增多多减少分区数,coalesce函数仅能将减少分数区

    scala> rdd.repartition(3).glom.collect
    res31: Array[Array[Int]] = Array(Array(5, 6), Array(7, 1, 2), Array(3, 4))
     
    scala> rdd.repartition(5).glom.collect
    res34: Array[Array[Int]] = Array(Array(5, 6), Array(1, 7, 2), Array(3), Array(), Array(4))
     
    scala> rdd.coalesce(3).glom.collect
    res32: Array[Array[Int]] = Array(Array(1), Array(2, 3), Array(4, 5, 6, 7))
     
    scala> rdd.coalesce(5).glom.collect
    res33: Array[Array[Int]] = Array(Array(1), Array(2, 3), Array(4, 5), Array(6, 7))   //仍然是4个分区

2.5、设置分区数

在创建RDD的函数中,可以传入第二个参数指定分区数

    scala> val rdd = sc.parallelize(List(1,2,3,4,5,6,7),3)
    rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[54] at parallelize at <console>:24
     
    scala> rdd.partitions.size
    res36: Int = 3

3、分区数


3.1、并行化集合

    Local模式

通过进入spark-shell时指定local[n]可以设置启动线程数,每个线程拥有1个内核,以此模拟分布式。默认为1个线程,即1个分区

    $ spark-shell --master local[3]
    scala> sc.parallelize(List(1,2,3)).partitions.size
    res0: Int = 3

    standalone模式

standalone模式下分区数为执行器线程的数量,也就是Worker的数量,但不能少于2个,即max(2,WorkerNum)

例子见上

    yarn模式

yarn模式下分区数默认为2,在进入spark shell时可以通过参数--num-executors设置执行器数量

    $ spark-shell --master yarn
     
    scala> sc.parallelize(List(1,2,3)).partitions.size
    res0: Int = 2
     
    $ spark-shell --master yarn --num-executors 3
     
    scala> sc.parallelize(List(1,2,3)).partitions.size
    res0: Int = 3

3.2、外部数据集textFile

通过textFile创建的RDD的分区数与模式无关

若调用textFile函数时不传入分区参数,则默认为2个分区

      //读取集群上或本地文件,并返回一个RDD
      def textFile(
          path: String,
          //若不传入指定分区数,则使用默认值defaultMinPartitions
          minPartitions: Int = defaultMinPartitions): RDD[String] = withScope {
        assertNotStopped()
        hadoopFile(path, classOf[TextInputFormat], classOf[LongWritable], classOf[Text],
          minPartitions).map(pair => pair._2.toString).setName(path)
      }

    //defaultParallelism为默认并行线程数,即内核数。默认最小分区数不会超过2
    def defaultMinPartitions: Int = math.min(defaultParallelism, 2)

textFile中调用了hadoopFile函数,这里采用的是TextInputFormat(key为LongWritable类型即行号,value为Text类型即行数据),所以说外部数据集的分区底层采用的是MR中Mapper端的文件逻辑切片

      def hadoopFile[K, V](
          path: String,
          inputFormatClass: Class[_ <: InputFormat[K, V]],
          keyClass: Class[K],
          valueClass: Class[V],
          minPartitions: Int = defaultMinPartitions): RDD[(K, V)] = withScope {
        assertNotStopped()
     
        // 强制加载hdfs-site.xml
        FileSystem.getLocal(hadoopConfiguration)
     
        // Hadoop配置文件大概有10KB大小,这已经相当大了,所以广播它
        val confBroadcast = broadcast(new SerializableConfiguration(hadoopConfiguration))
        // 设置输入路径
        val setInputPathsFunc = (jobConf: JobConf) => FileInputFormat.setInputPaths(jobConf, path)
     
        // 构造RDD
        new HadoopRDD(
          this,
          confBroadcast,
          Some(setInputPathsFunc),
          inputFormatClass,
          keyClass,
          valueClass,
          minPartitions).setName(path)
      }

在hadoopRDD.scala中可见大多采用的是MR中老版API:org.apache.hadoop.mapred.*

hadoopRDD中的分区函数

      //RDD的分区中调用了FileInputFormat.getSplits()
      override def getPartitions: Array[Partition] = {
        val jobConf = getJobConf()
        SparkHadoopUtil.get.addCredentials(jobConf)
        val inputFormat = getInputFormat(jobConf)
        //获取文件所有分片
        val inputSplits = inputFormat.getSplits(jobConf, minPartitions)
        val array = new Array[Partition](inputSplits.size)
        //将每个分片中数据写入到RDD分区中
        for (i <- 0 until inputSplits.size) {
          array(i) = new HadoopPartition(id, i, inputSplits(i))
        }
        array
      }

由于textFile文件采用的是老api中的FileInputFormat

    //注:以下RDD分区即为文件分片,省略了部分日志、异常有关代码
      public InputSplit[] getSplits(JobConf job, int numSplits)
        throws IOException {
     
        FileStatus[] files = listStatus(job);
        
        long totalSize = 0;                           
        for (FileStatus file: files) {                
          //获取文件总长度
          totalSize += file.getLen();
        }
     
        //计算每个分区(分片)中目标数据大小,numsplit为指定分区数
        long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
        //最小分区内数据大小为1字节
        long minSize = Math.max(job.getLong(org.apache.hadoop.mapreduce.lib.input.
          FileInputFormat.SPLIT_MINSIZE, 1), minSplitSize);
     
        // 生成分区
        ArrayList<FileSplit> splits = new ArrayList<FileSplit>(numSplits);
        for (FileStatus file: files) {
          Path path = file.getPath();
          long length = file.getLen();
          if (length != 0) {
            FileSystem fs = path.getFileSystem(job);
        //若文件可分区
            if (isSplitable(fs, path)) {
          //获取默认数据块大小,128M
              long blockSize = file.getBlockSize();
          //计算分区大小,computeSplitSize方法获取了目标分区中大小、最小分区大小(1B)、块大小(128M)中的中间值
              long splitSize = computeSplitSize(goalSize, minSize, blockSize);
              
          //最初待分区数据大小即文件大小
              long bytesRemaining = length;
          //SPLIT_SLOP分区槽为1.1,也就是说最后一个分区大小为1.1倍splitSize之内
              while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
                String[] splitHosts = getSplitHosts(blkLocations,
                    length-bytesRemaining, splitSize, clusterMap);
            //增加分区,并将一个splitSize写入到分区内
                splits.add(makeSplit(path, length-bytesRemaining, splitSize,
                    splitHosts));
                //剩余未分区数据减去一个分区大小
                bytesRemaining -= splitSize;
              }
     
              //将剩余未达到1.1倍分区大小的数据作为最后一个分区
              if (bytesRemaining != 0) {
                String[] splitHosts = getSplitHosts(blkLocations, length
                    - bytesRemaining, bytesRemaining, clusterMap);
                splits.add(makeSplit(path, length - bytesRemaining, bytesRemaining,
                    splitHosts));
              }
            } else {
          //若不可分区,将文件内容全写入一个分区中
              String[] splitHosts = getSplitHosts(blkLocations,0,length,clusterMap);
              splits.add(makeSplit(path, 0, length, splitHosts));
            }
          } else {
            //文件为空,则创建一个空的分区
            splits.add(makeSplit(path, 0, length, new String[0]));
          }
        }
        return splits.toArray(new FileSplit[splits.size()]);
      }

举个例子

文件大小为100B,通过sc.textFile("...", 8)设置目标分区为8个则:

totalSize = 100
numSplits = 8
goalSize = totalSize / numSplits = 100/8 = 12
minSize = 1
blockSize = 128M
splitSize = max(minSize, min(goalSize,blockSize)) = 12
splitsNum = (totalSize / splitSize).ceil = 9

4、新版API中FileInputFormat的分片

由于老版的FileInputFormat中是可以传入目标分片数的,这与spark中外部文件RDD自定义分区数的需求比较类似。新Api中分片大小默认为128M,这可能对于1个文件在一个节点中的内存占用过多,而更改分片大小比较麻烦。而老API可以很方便地设置小分区,所以spark采用来老API进行分区

最后回顾一下新API中的FileInputFormat.getSplits(),大部分与老API类似,只有计算分片大小部分不同

        //默认最小分片大小为1字节
        long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
        //默认最大分片大小为Long最大值
        long maxSize = getMaxSplitSize(job);
        //块大小,128M
        long blockSize = file.getBlockSize();
        //分片大小默认为以上3个值的中间值,即128M
        //一般blockSize不会改变,为了获取较小的分片,需要改变maxSize小于blockSize,spark索性采用老API
        long splitSize = computeSplitSize(blockSize, minSize, maxSize);

原文链接:https://blog.csdn.net/qq_39192827/article/details/97494565

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值