Hadoop是怎麼分塊的?
第一部分就是資料的劃分(即把File劃分成Block),這個是物理上真真實實的進行了劃分,資料檔案上傳到HDFS裡的時候,需要劃分成一塊一塊,每塊的大小由hadoop-default.xml裡配置選項進行劃分。
<property> <name>dfs.block.size</name> <value>67108864</value> <description>The default block size for new files.</description> </property>
這個就是預設的每個塊64MB。資料劃分的時候有冗餘,個數是由以下配置指定的。
<property> <name>dfs.replication</name> <value>3</value> <description>Default block replication. The actual number of replications can be specified when the file is created. The default is used if replication is not specified in create time. </description> </property>
具體的物理劃分步驟由Namenode決定。
第二種劃分是由InputFormat這個介面來定義的,其中有個getSplits方法。這裡有一個新的概念:fileSplit。每個map處理一個fileSplit,所以有多少個fileSplit就有多少個map(map數並不是單純的由使用者設定決定的)。
我們來看一下hadoop分配splits的原始碼:
if ((length != 0) && isSplitable(fs, path)) { long blockSize = file.getBlockSize(); long splitSize = computeSplitSize(goalSize, minSize, blockSize); long bytesRemaining = length; while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) { int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining); splits.add(new FileSplit(path, length-bytesRemaining, splitSize, blkLocations[blkIndex].getHosts())); bytesRemaining -= splitSize; } if (bytesRemaining != 0) { splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining, blkLocations[blkLocations.length-1].getHosts())); } } else if(length!=0) { splits.add(new FileSplit(path,0,length,blkLocations[0].getHosts())); }else{ // Create empty hosts array for zero length files splits.add(new FileSplit(path,0,length,new String[0])); }
從程式碼可以看出,一個塊為一個splits,即一個map,只要搞清楚一個塊的大小,就能計算出執行時的map數。而一個split的大小是由goalSize, minSize, blockSize這三個值決定的。computeSplitSize的邏輯是,先從goalSize和blockSize兩個值中選出最小的那個(比如一般不設定map數,這時blockSize為當前檔案的塊size,而goalSize是檔案大小除以使用者設定的map數得到的,如果沒設定的話,預設是1),在預設的大多數情況下,blockSize比較小。然後再取bloceSize和minSize中最大的那個。而minSize如果不通過”mapred.min.split.size”設定的話(”mapred.min.split.size”預設為0),minSize為1,這樣得出的一個splits的size就是blockSize,即一個塊一個map,有多少塊就有多少map。
上面說的是splitable的情況,unsplitable可以根據實際情況來計算,一般為一個檔案一個map。
下面是摘自網上的一個總結:
幾個簡單的結論:
1. 一個split不會包含零點幾或者幾點幾個Block,一定是包含大於等於1個整數個Block
2. 一個split不會包含兩個File的Block,不會跨越File邊界
3. split和Block的關係是一對多的關係
4. maptasks的個數最終決定於splits的長度
還有一點需要說明,在FileSplit類中,有一項是private String[] hosts;
看上去是說明這個FileSplit是放在哪些機器上的,實際上hosts裡只是儲存了一個Block的冗餘機器列表。
比如有個fileSplit 有4個block: Block11, Block12, Block13,Block14,這個FileSplit中的hosts裡最終儲存的是Block11本身和其備份所在的機器列表,也就是說 Block12,Block13,Block14存在哪些機器上沒有在FileSplit中記錄。
FileSplit中的這個屬性有利於排程作業時候的資料本地性問題。如果一個tasktracker前來索取task,jobtracker就會找個 task給他,找到一個maptask,得先看這個task的輸入的FileSplit裡hosts是否包含tasktracker所在機器,也就是判斷 和該tasktracker同時存在一個機器上的datanode是否擁有FileSplit中某個Block的備份。
但總之,只能牽就一個Block,其他Block就要從網路上傳。不過對於預設大多數情況下的一個block對應一個map,可以通過修改hosts使map的本地化數更多一些。 在講block的hosts傳給fileSplit時,hosts中的主機地址可以有多個,表示map可以從優先從這些hosts中選取(只是優先,但hdfs還很可能根據當時的網路負載選擇不是hosts中的主機起map task)。
知道這個特性之後,可以修改傳回給fileSplit的hosts,在列表中只寫block所在的那些hosts,這樣hdfs就會優先將這些map放到這些hosts上去執行,由於hosts上有該block,就省掉了網路傳輸資料的時間。
這樣做的話,在job很多的時候,可能會出現hot spot,即資料用的越多,它所在hosts上的map task就會越多。所以在考慮修改傳給fileSplit的時候要考慮平衡諸多因素