Spark之textFile切片详解

textFile

spark所有基于文件的输入方法,都支持目录读取、压缩文件、和通配符,

​ 比如:

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

texeFile(1,2)
该方法还采用可选的第二个参数来控制文件的分区数(但并不是最终的分区数就是这个设置的值),分区规则可以参考源码。

接下来我们捋一下它的流程。

首先按住crtl,鼠标左键点击进入textFile方法,

1.查看textFile方法的定义

 第一个参数是传入的文件路径
 第二个参数是最小的分区数量(并不代表是最终的分区数量)
 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的第二个参数后,minPartitions就是我们指定的参数
如果我们不指定的话,它会使用defaultMinPartitions。

我们进入defaultMinPartitions,看看它是怎么定义的

def defaultMinPartitions: Int = math.min(defaultParallelism, 2)
默认并行度和2取最小值,那么defaultParallelism是什么???接下来我们继续进入defaultParallelism
def defaultParallelism: Int = {
    assertNotStopped()
    taskScheduler.defaultParallelism
}
它调用了taskScheduler的默认并行度,我们继续深入

似乎进入了一个死胡同

def defaultParallelism(): Int
我们左键点击到defaultParallelism,然后按alt+shift+h(idea的快捷键)查看它的实现类里面具体方法

Spark之textFile切片详解_第1张图片

来到了这里

override def defaultParallelism(): Int = backend.defaultParallelism()
继续点defaultParallelism

仿佛又来到了一个死胡同

Spark之textFile切片详解_第2张图片

没关系,选中defaultParallelism,按alt+shift+h

Spark之textFile切片详解_第3张图片

有两个选项,一个是本地环境下,一个是集群下,我们进本地,因为在本地测试的。

override def defaultParallelism(): Int =  scheduler.conf.getInt("spark.default.parallelism", totalCores)
发现到底了,那么这个totalCores大概是从哪里获得的呢

点conf后大概知道,来自我们的sparkconf和sparkcontext

我们进入SparkContext

Spark之textFile切片详解_第4张图片

大概是从这里设置的。

最后我们得到了我们的defaultParallelism,在本地情况下就是我们设置的local[2]里面的这个值。

def defaultMinPartitions: Int = math.min(defaultParallelism, 2)
用我们设置的值,和默认的defaultParallelism,取一个最小值。

得到之后我们进入hadoopFile方法(第一个刚开始那里)

2.然后我们进去hadoopFile

在里面new了一个HadoopRDD,我们点进去

进入HadoopRDD之后我们找到一个getPartitions方法

override def getPartitions: Array[Partition] = {
    val jobConf = getJobConf()
    // add the credentials here as this can be called before SparkContext initialized
    SparkHadoopUtil.get.addCredentials(jobConf)
    val inputFormat = getInputFormat(jobConf)
    val inputSplits = inputFormat.getSplits方法(jobConf, minPartitions)
    val array = new Array[Partition](inputSplits.size)
    for (i <- 0 until inputSplits.size) {
      array(i) = new HadoopPartition(id, i, inputSplits(i))
    }
    array
  }
  
 在这里面我们可以看到最后返回的分区 来自val array = new Array[Partition](inputSplits.size)
 我们看看inputSplits的数量是怎么来的。
 进入getSplits方法,按alt+shift+h,选择FileInputFormat,spark2.2.0使用的是老版本的FileInputFormat(org.apache.hadoop.mapred)

3.最终来到了终极大Boss的地方

我们一步一步来看。
public InputSplit[] getSplits(JobConf job, int numSplits) throws IOException {
        Stopwatch sw = (new Stopwatch()).start();
        //这里我们得到了我们传入路径的文件列表
        FileStatus[] files = this.listStatus(job);
        job.setLong("mapreduce.input.fileinputformat.numinputfiles", (long)files.length);
        //这里定义的是记录文件大小总字节数
        long totalSize = 0L;
        FileStatus[] arr$ = files;
        int len$ = files.length;
		//接下来循环我们目录下的每个文件
        for(int i$ = 0; i$ < len$; ++i$) {
            FileStatus file = arr$[i$];
            if (file.isDirectory()) {
                throw new IOException("Not a file: " + file.getPath());
            }
            totalSize += file.getLen();
        }
		//我们得到了总的字节数,totalSize
		//这里我们定义一个理想化的切片目标大小,总的大小/我们得到的defaultParallelism
		//大概意思是想保证每个片的大小能够保证均分
        long goalSize = totalSize / (long)(numSplits == 0 ? 1 : numSplits);
        //这里是定义了一个最小的切片字节大小
        long minSize = Math.max(job.getLong("mapreduce.input.fileinputformat.split.minsize", 1L), this.minSplitSize);
        //这里是初始化了一个理想的切片数组
        ArrayList splits = new ArrayList(numSplits);
        NetworkTopology clusterMap = new NetworkTopology();
        FileStatus[] arr$ = files;
        int len$ = files.length;
		//开始对每个文件进行切分
        for(int i$ = 0; i$ < len$; ++i$) {
            FileStatus file = arr$[i$];
            Path path = file.getPath();
            long length = file.getLen();
            if (length == 0L) {
                splits.add(this.makeSplit(path, 0L, length, new String[0]));
            } else {
                FileSystem fs = path.getFileSystem(job);
                BlockLocation[] blkLocations;
                if (file instanceof LocatedFileStatus) {
                    blkLocations = ((LocatedFileStatus)file).getBlockLocations();
                } else {
                    blkLocations = fs.getFileBlockLocations(file, 0L, length);
                }
				//判断是不是可以切割的
                if (!this.isSplitable(fs, path)) {
                    String[][] splitHosts = this.getSplitHostsAndCachedHosts(blkLocations, 0L, length, clusterMap);
                    splits.add(this.makeSplit(path, 0L, length, splitHosts[0], splitHosts[1]));
                } else {
                	//如果可以切割的话
                	//首先得到这个文件的字节大小
                    long blockSize = file.getBlockSize();
                    //我们进入computeSpliSize方法进去看看
                    //Math.max(minSize, Math.min(goalSize, blockSize));
                    //它是首先用我们理想切片大小和块大小取一个最小的值,因为我们的理想切片大小,
                    //	肯定不能比我们的块大小大。
                    //  注意一下如果是windows本地的块大小是32m,如果是hdsf的话1.x,2.x,3.x
                    //  分别是64m,128m,256m
                    //然后再用得到的值与minSize取最大值,意思是保证切片大小不能比我们设置的最小值还小
                    long splitSize = this.computeSplitSize(goalSize, minSize, blockSize);
                    long bytesRemaining;
                    String[][] splitHosts;
                    //得到最终的切片大小之后,我们开始切了。
                    //这里有一个默认的1.1的阈值,意思是说如果我们最中切块剩下的字节大小
                    //  比我们的切片大小1.1倍小的话,我们就不把剩下的0.1再分到另一个片了,允许有10%的冗余
                    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]));
                    }

                    if (bytesRemaining != 0L) {
                        splitHosts = this.getSplitHostsAndCachedHosts(blkLocations, length - bytesRemaining, bytesRemaining, clusterMap);
                        splits.add(this.makeSplit(path, length - bytesRemaining, bytesRemaining, splitHosts[0], splitHosts[1]));
                    }
                }
            }
        }

        sw.stop();
        if (LOG.isDebugEnabled()) {
            LOG.debug("Total # of splits generated by getSplits: " + splits.size() + ", TimeTaken: " + sw.elapsedMillis());
        }
		//最终我们得到了最后的切片数量,一步一步返回。
        return (InputSplit[])splits.toArray(new FileSplit[splits.size()]);
}

最后捋一下,
首先我们使用textFile方法,有两个参数,第一个是文件路径,第二个是numpartitions。
如果我们不传第二个参数的话,minpartitions数就是采取默认的(用local指定的并行数和2取最小值)
如果我们传入第二个参数后,numpartitions会和参数保持一致。
,最终一步一步的传入到hadoop的切片处。

你可能感兴趣的:(spark基础)