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的快捷鍵)查看它的實現類裏面具體方法

在這裏插入圖片描述

來到了這裏

override def defaultParallelism(): Int = backend.defaultParallelism()
繼續點defaultParallelism

彷彿又來到了一個死衚衕

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-49Uzh6sj-1573218572809)(C:\Users\luoyunfan\AppData\Roaming\Typora\typora-user-images\1573215505015.png)]

沒關係,選中defaultParallelism,按alt+shift+h

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9ORWgcLG-1573218572811)(C:\Users\luoyunfan\AppData\Roaming\Typora\typora-user-images\1573215704179.png)]

有兩個選項,一個是本地環境下,一個是集羣下,我們進本地,因爲在本地測試的。

override def defaultParallelism(): Int =  scheduler.conf.getInt("spark.default.parallelism", totalCores)
發現到底了,那麼這個totalCores大概是從哪裏獲得的呢

點conf後大概知道,來自我們的sparkconf和sparkcontext

我們進入SparkContext

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aaGCfnUN-1573218572819)(C:\Users\luoyunfan\AppData\Roaming\Typora\typora-user-images\1573216659690.png)]

大概是從這裏設置的。

最後我們得到了我們的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<FileSplit> 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的切片處。

發佈了91 篇原創文章 · 獲贊 9 · 訪問量 3678
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章