Hadoop是怎麼分塊的(R1)

thamsyangsw發表於2014-03-11
轉載地址:http://www.cnblogs.com/xuxm2007/archive/2011/09/01/2162011.html

hadoop的分塊有兩部分,其中第一部分更為人熟知一點。
 
第一部分就是資料的劃分(即把File劃分成Block),這個是物理上真真實實的進行了劃分,資料檔案上傳到HDFS裡的時候,需要劃分成一塊一塊,每塊的大小由hadoop-default.xml裡配置選項進行劃分。
 

  dfs.block.size
  67108864
  The default block size for new files.
 
這個就是預設的每個塊64MB。
資料劃分的時候有冗餘,個數是由

  dfs.replication
  3
  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.
 

指定的。
 
具體的物理劃分步驟要看Namenode,這裡要說的是更有意思的hadoop中的第二種劃分。
 
在hadoop中第二種劃分是由InputFormat這個介面來定義的,其中有個getSplits方法。這裡就有了一個新的不為人熟知的概念:Split。Split的作用是什麼,Split和Block是什麼關係,下面就可以說明清楚。
在Hadoop0.1中,split劃分是在JobTracker端完成的,發生在JobInitThread對JobInProgress呼叫inittasks()的時候;而在0.18.3中是由JobClient完成的,JobClient劃分好後,把split.file寫入hdfs裡,到時候jobtracker端只需要讀這個檔案,就知道Split是怎麼劃分的了。
第二種劃分只是一種邏輯上劃分,目的是為了讓Map Task更好的獲取資料輸入,仔細分析如下這個場景:
 
File 1 : Block11, Block 12, Block 13, Block 14, Block 15
File 2 : Block21, Block 22, Block 23
 
File1有5個Block,最後一個Block當然可能小於64MB;File2有3個Block
 
如果使用者在程式中指定map tasks的個數,比如說是2(如果不指定的話maptasks個數預設是1),那麼在
FileInputFormat(最常見的InputFormat實現)的getSplits方法中,首先會計算totalSize=8(可以對照原始碼看看,注意getSplits這個函式里的計量單位是Block個數,而不是Byte個數,後面有個變數叫bytesremaining仍然表示剩餘的Block個數,有些變數名讓人無語),然後會計算goalSize=totalSize/numSplits=4,對於File1,計算一個Split有多少個Block是這樣計算的
 
long splitSize = computeSplitSize(goalSize, minSize, blockSize);
protected long computeSplitSize(long goalSize, long minSize, long blockSize) {
 return Math.max(minSize, Math.min(goalSize, blockSize));
}
這裡minSize是1(說明了一個Split至少包含一個Block,不會出現一個Split包含零點幾個Block的情況),計算得出splitSize=4,所以接下來Split劃分是這樣分的:
Split 1: Block11, Block12, Block13,Block14
Split 2: Block15
Split 3: Block21, Block22, Block23
那使用者指定的map個數是2,出現了三個split怎麼辦?在JobInProgress裡其實maptasks的個數是根據Splits的長度來指定的,所以使用者指定的map個數只是個參考。可以參看JobInProgress: initTasks()
裡的程式碼:
 
  try {
   splits = JobClient.readSplitFile(splitFile);
  } finally {
   splitFile.close();
  }
  numMapTasks = splits.length;
  maps = new TaskInProgress[numMapTasks];
 
所以問題就很清晰了,還如果使用者指定了20個map作業,那麼最後會有8個Split(每個Split一個Block),所以最後實際上就有8個MapTasks,也就是說maptask的個數是由splits的長度決定的。
 
幾個簡單的結論:
1. 一個split不會包含零點幾或者幾點幾個Block,一定是包含大於等於1個整數個Block
2. 一個split不會包含兩個File的Block,不會跨越File邊界
3. split和Block的關係是一對多的關係
4. maptasks的個數最終決定於splits的長度
 
 
還有一點需要說明,在FileSplit類中,有一項是private String[] hosts;
看上去是說明這個FileSplit是放在哪些機器上的,實際上hosts裡只是儲存了一個Block的冗餘機器列表。
比如上面例子中的Split 1: 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就從網路上傳罷。

================================================================================================================== 

hadoop的分塊有兩部分。

第一部分就是資料的劃分(即把File劃分成Block),這個是物理上的劃分,資料檔案上傳到HDFS裡的時候,需要劃分成一塊一塊,每塊的大小由hadoop-default.xml裡配置選項進行劃分(大小不足一塊時,便按實際大小存放):

dfs.block.size

67108864

The default block size for new files.

這裡設定的是每個塊64MB。
資料劃分的時候也可以設定備份的份數:

dfs.replication

3

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.  


具體的物理劃分步驟由Namenode決定,下面hadoop中的第二種劃分,用來決定M/R執行時,一個map處理的資料量。

在hadoop中第二種劃分是由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的時候要考慮平衡諸多因素

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26613085/viewspace-1107142/,如需轉載,請註明出處,否則將追究法律責任。

相關文章