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
彷彿又來到了一個死衚衕
沒關係,選中defaultParallelism,按alt+shift+h
有兩個選項,一個是本地環境下,一個是集羣下,我們進本地,因爲在本地測試的。
override def defaultParallelism(): Int = scheduler.conf.getInt("spark.default.parallelism", totalCores)
發現到底了,那麼這個totalCores大概是從哪裏獲得的呢
點conf後大概知道,來自我們的sparkconf和sparkcontext
我們進入SparkContext
大概是從這裏設置的。
最後我們得到了我們的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的切片處。