Hadoop源代碼學習(完整版)

源地址:http://caibinbupt.iteye.com/blog/280790

注:再對照Hadoop源代碼的學習過程中,我對這個文檔也做了一些修改,用藍色字體標註。

Hadoop源代碼學習(完整版)

經濟不行啦,只好潛心研究技術。 
Google的核心競爭技術是它的計算平臺。Google的大牛們用了下面5篇文章,介紹了它們的計算設施。 
GoogleCluster: http://research.google.com/archive/googlecluster.html 
Chubby:http://labs.google.com/papers/chubby.html 
GFS:http://labs.google.com/papers/gfs.html 
BigTable:http://labs.google.com/papers/bigtable.html 
MapReduce:http://labs.google.com/papers/mapreduce.html 
很快,Apache上就出現了一個類似的解決方案,目前它們都屬於Apache的Hadoop項目,對應的分別是: 
Chubby-->ZooKeeper 
GFS-->HDFS 
BigTable-->HBase 
MapReduce-->Hadoop 
目前,基於類似思想的Open Source項目還很多,如Facebook用於用戶分析的Hive。 
HDFS作爲一個分佈式文件系統,是所有這些項目的基礎。分析好HDFS,有利於瞭解其他系統。由於Hadoop的HDFS和MapReduce是同一個項目,我們就把他們放在一塊,進行分析。

下圖是MapReduce整個項目的頂層包圖和他們的依賴關係。Hadoop包之間的依賴關係比較複雜,原因是HDFS提供了一個分佈式文件系統,該系統提供API,可以屏蔽本地文件系統和分佈式文件系統,甚至象Amazon S3這樣的在線存儲系統。這就造成了分佈式文件系統的實現,或者是分佈式文件系統的底層的實現,依賴於某些貌似高層的功能。功能的相互引用,造成了蜘蛛網型的依賴關係。一個典型的例子就是包conf,conf用於讀取系統配置,它依賴於fs,主要是讀取配置文件的時候,需要使用文件系統,而部分的文件系統的功能,在包fs中被抽象了。

Hadoop的關鍵部分集中於圖中藍色部分,這也是我們考察的重點。

 

下面給出了Hadoop的包的功能分析。

 

Package

Dependences

tool

提供一些命令行工具,如DistCp,archive

mapreduce

Hadoop的Map/Reduce實現

filecache

提供HDFS文件的本地緩存,用於加快Map/Reduce的數據訪問速度

fs

文件系統的抽象,可以理解爲支持多種文件系統實現的統一文件訪問接口

hdfs

HDFS,Hadoop的分佈式文件系統實現

ipc

一個簡單的IPC的實現,依賴於io提供的編解碼功能

參考:http://zhangyu8374.iteye.com/blog/86306

io

表示層。將各種數據編碼/解碼,方便於在網絡上傳輸

net

封裝部分網絡功能,如DNS,socket

security

用戶和用戶組信息

conf

系統的配置參數

metrics

系統統計數據的收集,屬於網管範疇

util

工具類

record

根據DDL(數據描述語言)自動生成他們的編解碼函數,目前可以提供C++和Java

http

基於Jetty的HTTP Servlet,用戶通過瀏覽器可以觀察文件系統的一些狀態信息和日誌

log

提供HTTP訪問日誌的HTTP Servlet


由於Hadoop的MapReduce和HDFS都有通信的需求,需要對通信的對象進行序列化。Hadoop並沒有採用Java的序列化,而是引入了它自己的系統。

org.apache.hadoop.io中定義了大量的可序列化對象,他們都實現了Writable接口。實現了Writable接口的一個典型例子如下:

Java代碼  收藏代碼
  1. public class MyWritable implements Writable {     
  2.     // Some data          
  3.     private int counter;     
  4.     private long timestamp;     
  5.     
  6.     public void write(DataOutput out) throws IOException {     
  7.         out.writeInt(counter);     
  8.         out.writeLong(timestamp);     
  9.     }     
  10.         
  11.     public void readFields(DataInput in) throws IOException {     
  12.         counter = in.readInt();     
  13.         timestamp = in.readLong();     
  14.     }     
  15.     
  16.     public static MyWritable read(DataInput in) throws IOException {     
  17.         MyWritable w = new MyWritable();     
  18.         w.readFields(in);     
  19.         return w;     
  20.     }     
  21. }   

 

 

 

其中的write和readFields分別實現了把對象序列化和反序列化的功能,是Writable接口定義的兩個方法。下圖給出了龐大的org.apache.hadoop.io中對象的關係。

 

 

 

 

 

 

 

 

這裏,我把ObjectWritable標爲紅色,是因爲相對於其他對象,它有不同的地位。當我們討論Hadoop的RPC時,我們會提到RPC上交換的信息,必須是Java的基本類型,String和Writable接口的實現類,以及元素爲以上類型的數組。ObjectWritable對象保存了一個可以在RPC上傳輸的對象和對象的類型信息。這樣,我們就有了一個萬能的,可以用於客戶端/服務器間傳輸的Writable對象。例如,我們要把上面例子中的對象作爲RPC請求,需要根據MyWritable創建一個ObjectWritable,ObjectWritable往流裏會寫如下信息

對象類名長度,對象類名,對象自己的串行化結果

這樣,到了對端,ObjectWritable可以根據對象類名創建對應的對象,並解串行。應該注意到,ObjectWritable依賴於WritableFactories,那存儲了Writable子類對應的工廠。我們需要把MyWritable的工廠,保存在WritableFactories中(通過WritableFactories.setFactory)。


爲org.apache.hadoop.io.compress等的分析預留位置


介紹完org.apache.hadoop.io以後,我們開始來分析org.apache.hadoop.rpc。RPC採用客戶機/服務器模式。請求程序就是一個客戶機,而服務提供程序就是一個服務器。當我們討論HDFS的,通信可能發生在:

  • Client-NameNode之間,其中NameNode是服務器
  • Client-DataNode之間,其中DataNode是服務器
  • DataNode-NameNode之間,其中NameNode是服務器
  • DataNode-DateNode之間,其中某一個DateNode是服務器,另一個是客戶端

如果我們考慮Hadoop的Map/Reduce以後,這些系統間的通信就更復雜了。爲了解決這些客戶機/服務器之間的通信,Hadoop引入了一個RPC框架。該RPC框架利用的Java的反射能力,避免了某些RPC解決方案中需要根據某種接口語言(如CORBA的IDL)生成存根和框架的問題。但是,該RPC框架要求調用的參數和返回結果必須是Java的基本類型,String和Writable接口的實現類,以及元素爲以上類型的數組。同時,接口方法應該只拋出IOException異常。(參考自http://zhangyu8374.iteye.com/blog/86306

既然是RPC,當然就有客戶端和服務器,當然,org.apache.hadoop.rpc也就有了類Client和類Server。但是類Server是一個抽象類,類RPC封裝了Server,利用反射,把某個對象的方法開放出來,變成RPC中的服務器。

下圖是org.apache.hadoop.rpc的類圖。



既然是RPC,自然就有客戶端和服務器,當然,org.apache.hadoop.rpc也就有了類Client和類Server。在這裏我們來仔細考察org.apache.hadoop.rpc.Client。下面的圖包含了org.apache.hadoop.rpc.Client中的關鍵類和關鍵方法。

由於Client可能和多個Server通信,典型的一次HDFS讀,需要和NameNode打交道,也需要和某個/某些DataNode通信。這就意味着某一個Client需要維護多個連接。同時,爲了減少不必要的連接,現在Client的做法是拿ConnectionId(圖中最右側)來做爲Connection的ID。ConnectionId包括一個InetSocketAddress(IP地址+端口號或主機名+端口號)對象和一個用戶信息對象。這就是說,同一個用戶到同一個InetSocketAddress的通信將共享同一個連接。

 

 

 

連接被封裝在類Client.Connection中,所有的RPC調用,都是通過Connection,進行通信。一個RPC調用,自然有輸入參數,輸出參數和可能的異常,同時,爲了區分在同一個Connection上的不同調用,每個調用都有唯一的id。調用是否結束也需要一個標記,所有的這些都體現在對象Client.Call中。Connection對象通過一個Hash表,維護在這個連接上的所有Call:

Java代碼  收藏代碼
  1. private Hashtable<Integer, Call> calls = new Hashtable<Integer, Call>();  

 

一個RPC調用通過addCall,把請求加到Connection裏。爲了能夠在這個框架上傳輸Java的基本類型,String和Writable接口的實現類,以及元素爲以上類型的數組,我們一般把Call需要的參數打包成爲ObjectWritable對象。

Client.Connection會通過socket連接服務器,連接成功後回校驗客戶端/服務器的版本號(Client.ConnectionwriteHeader()方法),校驗成功後就可以通過Writable對象來進行請求的發送/應答了。注意,每個Client.Connection會起一個線程,不斷去讀取socket,並將收到的結果解包,找出對應的Call,設置Call並通知結果已經獲取。

Call使用Obejct的wait和notify,把RPC上的異步消息交互轉成同步調用。

還有一點需要注意,一個Client會有多個Client.Connection,這是一個很自然的結果。因爲在Client類包含有一個成員變量connections,其完整定義爲“private Hashtable<ConnectionId, Connection> connections = new Hashtable<ConnectionId, Connection>();”,從這個語句可以看出,connections是一個hash表,包含多個Connection。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章