1. 程式人生 > >hadoop 檔案分塊,block與split關係

hadoop 檔案分塊,block與split關係

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,這裡要說的是更有意思的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就從網路上傳罷。