1. 程式人生 > 實用技巧 >Hadoop體系中,hive和hbase的區別,那麼什麼又是hdfs呢?

Hadoop體系中,hive和hbase的區別,那麼什麼又是hdfs呢?

首先理清hive和hbase的概念吧:

1、hive是什麼?
hive可以認為是map-reduce的一個包裝。
hive的意義就是把好寫的hive的sql(也叫hql)轉換為複雜難寫的map-reduce程式,從而降低使用Hadoop中使用map-reduce的難度。
Hive本身不儲存和計算資料,它完全依賴於HDFS和MapReduce,Hive中的表純邏輯(只是個邏輯表)

2、hbase是什麼?
hbase可以認為是hdfs的一個包裝。他的本質是資料儲存,是個NoSql資料庫;hbase部署於hdfs之上,並且克服了hdfs在隨機讀寫方面的缺點。
hbase可以理解為為hdfs建立了索引,查詢不走map-reduce,直接走自己的表
hbase是物理表,不是邏輯表,提供一個超大的記憶體hash表,搜尋引擎通過它來儲存索引,方便查詢操作。

深入理解HDFS:Hadoop分散式檔案系統

文字詳細介紹了HDFS中的許多概念,對於理解Hadoop分散式檔案系統很有幫助。

1. 介紹

在現代的企業環境中,單機容量往往無法儲存大量資料,需要跨機器儲存。統一管理分佈在叢集上的檔案系統稱為分散式檔案系統。而一旦在系統中,引入網路,就不可避免地引入了所有網路程式設計的複雜性,例如挑戰之一是如果保證在節點不可用的時候資料不丟失。

傳統的網路檔案系統(NFS)雖然也稱為分散式檔案系統,但是其存在一些限制。由於NFS中,檔案是儲存在單機上,因此無法提供可靠性保證,當很多客戶端同時訪問NFS Server時,很容易造成伺服器壓力,造成效能瓶頸。另外如果要對NFS中的檔案中進行操作,需要首先同步到本地,這些修改在同步到服務端之前,其他客戶端是不可見的。某種程度上,NFS不是一種典型的分散式系統,雖然它的檔案的確放在遠端(單一)的伺服器上面。

從NFS的協議棧可以看到,它事實上是一種VFS(作業系統對檔案的一種抽象)實現。

HDFS,是Hadoop Distributed File System的簡稱,是Hadoop抽象檔案系統的一種實現。Hadoop抽象檔案系統可以與本地系統、Amazon S3等整合,甚至可以通過Web協議(webhsfs)來操作。HDFS的檔案分佈在叢集機器上,同時提供副本進行容錯及可靠性保證。例如客戶端寫入讀取檔案的直接操作都是分佈在叢集各個機器上的,沒有單點效能壓力。

如果你從零開始搭建一個完整的叢集,參考[Hadoop叢集搭建詳細步驟(2.6.0)](http://blog.csdn.net/bingduanlbd/article/details/51892750

2. HDFS設計原則

HDFS設計之初就非常明確其應用場景,適用與什麼型別的應用,不適用什麼應用,有一個相對明確的指導原則。

2.1 設計目標

  • 儲存非常大的檔案:這裡非常大指的是幾百M、G、或者TB級別。實際應用中已有很多叢集儲存的資料達到PB級別。根據Hadoop官網,Yahoo!的Hadoop叢集約有10萬顆CPU,執行在4萬個機器節點上。更多世界上的Hadoop叢集使用情況,參考Hadoop官網.

  • 採用流式的資料訪問方式: HDFS基於這樣的一個假設:最有效的資料處理模式是一次寫入、多次讀取資料集經常從資料來源生成或者拷貝一次,然後在其上做很多分析工作
    分析工作經常讀取其中的大部分資料,即使不是全部。 因此讀取整個資料集所需時間比讀取第一條記錄的延時更重要。

  • 運行於商業硬體上: Hadoop不需要特別貴的、reliable的機器,可運行於普通商用機器(可以從多家供應商採購) 商用機器不代表低端機器在叢集中(尤其是大的叢集),節點失敗率是比較高的HDFS的目標是確保叢集在節點失敗的時候不會讓使用者感覺到明顯的中斷。

2.2 HDFS不適合的應用型別

有些場景不適合使用HDFS來儲存資料。下面列舉幾個:

1) 低延時的資料訪問
對延時要求在毫秒級別的應用,不適合採用HDFS。HDFS是為高吞吐資料傳輸設計的,因此可能犧牲延時HBase更適合低延時的資料訪問。

2)大量小檔案
檔案的元資料(如目錄結構,檔案block的節點列表,block-node mapping)儲存在NameNode的記憶體中, 整個檔案系統的檔案數量會受限於NameNode的記憶體大小。
經驗而言,一個檔案/目錄/檔案塊一般佔有150位元組的元資料記憶體空間。如果有100萬個檔案,每個檔案佔用1個檔案塊,則需要大約300M的記憶體。因此十億級別的檔案數量在現有商用機器上難以支援。

3)多方讀寫,需要任意的檔案修改
HDFS採用追加(append-only)的方式寫入資料。不支援檔案任意offset的修改。不支援多個寫入器(writer)。

3. HDFS核心概念

3.1 Blocks

物理磁碟中有塊的概念,磁碟的物理Block是磁碟操作最小的單元,讀寫操作均以Block為最小單元,一般為512 Byte。檔案系統在物理Block之上抽象了另一層概念,檔案系統Block物理磁碟Block的整數倍。通常為幾KB。Hadoop提供的df、fsck這類運維工具都是在檔案系統的Block級別上進行操作。

HDFS的Block塊比一般單機檔案系統大得多,預設為128M。HDFS的檔案被拆分成block-sized的chunk,chunk作為獨立單元儲存。比Block小的檔案不會佔用整個Block,只會佔據實際大小。例如, 如果一個檔案大小為1M,則在HDFS中只會佔用1M的空間,而不是128M。

HDFS的Block為什麼這麼大?
是為了最小化查詢(seek)時間,控制定位檔案與傳輸檔案所用的時間比例。假設定位到Block所需的時間為10ms,磁碟傳輸速度為100M/s。如果要將定位到Block所用時間佔傳輸時間的比例控制1%,則Block大小需要約100M。
但是如果Block設定過大,在MapReduce任務中,Map或者Reduce任務的個數 如果小於叢集機器數量,會使得作業執行效率很低。

Block抽象的好處
block的拆分使得單個檔案大小可以大於整個磁碟的容量,構成檔案的Block可以分佈在整個叢集, 理論上,單個檔案可以佔據叢集中所有機器的磁碟。
Block的抽象也簡化了儲存系統,對於Block,無需關注其許可權,所有者等內容(這些內容都在檔案級別上進行控制)。
Block作為容錯和高可用機制中的副本單元,即以Block為單位進行復制。

3.2 Namenode & Datanode

整個HDFS叢集由Namenode和Datanode構成master-worker(主從)模式。Namenode複雜構建名稱空間,管理檔案的元資料等,而Datanode負責實際儲存資料,負責讀寫工作。

Namenode

Namenode存放檔案系統樹及所有檔案、目錄的元資料。元資料持久化為2種形式:

  • namespcae image-
  • edit log

但是持久化資料中不包括Block所在的節點列表,及檔案的Block分佈在叢集中的哪些節點上,這些資訊是在系統重啟的時候重新構建(通過Datanode彙報的Block資訊)。
在HDFS中,Namenode可能成為叢集的單點故障,Namenode不可用時,整個檔案系統是不可用的。HDFS針對單點故障提供了2種解決機制:
1)備份持久化元資料
將檔案系統的元資料同時寫到多個檔案系統, 例如同時將元資料寫到本地檔案系統及NFS。這些備份操作都是同步的、原子的。

2)Secondary Namenode
Secondary節點定期合併主Namenode的namespace image和edit log, 避免edit log過大,通過建立檢查點checkpoint來合併。它會維護一個合併後的namespace image副本, 可用於在Namenode完全崩潰時恢復資料。下圖為Secondary Namenode的管理介面:

Secondary Namenode通常執行在另一臺機器,因為合併操作需要耗費大量的CPU和記憶體。其資料落後於Namenode,因此當Namenode完全崩潰時,會出現資料丟失。 通常做法是拷貝NFS中的備份元資料到Second,將其作為新的主Namenode。
在HA中可以執行一個Hot Standby,作為熱備份,在Active Namenode故障之後,替代原有Namenode成為Active Namenode。

Datanode

資料節點負責儲存和提取Block,讀寫請求可能來自namenode,也可能直接來自客戶端。資料節點週期性向Namenode彙報自己節點上所儲存的Block相關資訊。

3.3 Block Caching

DataNode通常直接從磁碟讀取資料,但是頻繁使用的Block可以在記憶體中快取。預設情況下,一個Block只有一個數據節點會快取。但是可以針對每個檔案可以個性化配置。
作業排程器可以利用快取提升效能,例如MapReduce可以把任務執行在有Block快取的節點上。
使用者或者應用可以向NameNode傳送快取指令(快取哪個檔案,快取多久), 快取池的概念用於管理一組快取的許可權和資源。

3.4 HDFS Federation

我們知道NameNode的記憶體會制約檔案數量,HDFS Federation提供了一種橫向擴充套件NameNode的方式。在Federation模式中,每個NameNode管理名稱空間的一部分,例如一個NameNode管理/user目錄下的檔案, 另一個NameNode管理/share目錄下的檔案。
每個NameNode管理一個namespace volumn,所有volumn構成檔案系統的元資料。每個NameNode同時維護一個Block Pool,儲存Block的節點對映等資訊。各NameNode之間是獨立的,一個節點的失敗不會導致其他節點管理的檔案不可用。
客戶端使用mount table將檔案路徑對映到NameNode。mount table是在Namenode群組之上封裝了一層,這一層也是一個Hadoop檔案系統的實現,通過viewfs:協議訪問。

3.5 HDFS HA

在HDFS叢集中,NameNode依然是單點故障(SPOF)。元資料同時寫到多個檔案系統以及Second NameNode定期checkpoint有利於保護資料丟失,但是並不能提高可用性。
這是因為NameNode是唯一一個對檔案元資料和file-block對映負責的地方, 當它掛了之後,包括MapReduce在內的作業都無法進行讀寫。

當NameNode故障時,常規的做法是使用元資料備份重新啟動一個NameNode。元資料備份可能來源於:

  • 多檔案系統寫入中的備份
  • Second NameNode的檢查點檔案

啟動新的Namenode之後,需要重新配置客戶端和DataNode的NameNode資訊。另外重啟耗時一般比較久,稍具規模的叢集重啟經常需要幾十分鐘甚至數小時,造成重啟耗時的原因大致有:
1) 元資料映象檔案載入到記憶體耗時較長。
2) 需要重放edit log
3) 需要收到來自DataNode的狀態報告並且滿足條件後才能離開安全模式提供寫服務。

Hadoop的HA方案

採用HA的HDFS叢集配置兩個NameNode,分別處於Active和Standby狀態。當Active NameNode故障之後,Standby接過責任繼續提供服務,使用者沒有明顯的中斷感覺。一般耗時在幾十秒到數分鐘。
HA涉及到的主要實現邏輯有

1) 主備需共享edit log儲存。
主NameNode和待命的NameNode共享一份edit log,當主備切換時,Standby通過回放edit log同步資料。
共享儲存通常有2種選擇

  • NFS:傳統的網路檔案系統
  • QJM:quorum journal manager

QJM是專門為HDFS的HA實現而設計的,用來提供高可用的edit log。QJM執行一組journal node,edit log必須寫到大部分的journal nodes。通常使用3個節點,因此允許一個節點失敗,類似ZooKeeper。注意QJM沒有使用ZK,雖然HDFS HA的確使用了ZK來選舉主Namenode。一般推薦使用QJM。

2)DataNode需要同時往主備傳送Block Report
因為Block對映資料儲存在記憶體中(不是在磁碟上),為了在Active NameNode掛掉之後,新的NameNode能夠快速啟動,不需要等待來自Datanode的Block Report,DataNode需要同時向主備兩個NameNode傳送Block Report。

3)客戶端需要配置failover模式(對使用者透明)
Namenode的切換對客戶端來說是無感知的,通過客戶端庫來實現。客戶端在配置檔案中使用的HDFS URI是邏輯路徑,對映到一對Namenode地址。客戶端會不斷嘗試每一個Namenode地址直到成功。

4)Standby替代Secondary NameNode
如果沒有啟用HA,HDFS獨立執行一個守護程序作為Secondary Namenode。定期checkpoint,合併映象檔案和edit日誌。

如果當主Namenode失敗時,備份Namenode正在關機(停止 Standby),運維人員依然可以從頭啟動備份Namenode,這樣比沒有HA的時候更省事,算是一種改進,因為重啟整個過程已經標準化到Hadoop內部,無需運維進行復雜的切換操作。

NameNode的切換通過代failover controller來實現。failover controller有多種實現,預設實現使用ZooKeeper來保證只有一個Namenode處於active狀態。

每個Namenode執行一個輕量級的failover controller程序,該程序使用簡單的心跳機制來監控Namenode的存活狀態並在Namenode失敗是觸發failover。Failover可以由運維手動觸發,例如在日常維護中需要切換主Namenode,這種情況graceful failover,非手動觸發的failover稱為ungraceful failover。

在ungraceful failover的情況下,沒有辦法確定失敗(被判定為失敗)的節點是否停止執行,也就是說觸發failover後,之前的主Namenode可能還在執行。QJM一次只允許一個Namenode寫edit log,但是之前的主Namenode仍然可以接受讀請求。Hadoop使用fencing來殺掉之前的Namenode。Fencing通過收回之前Namenode對共享的edit log的訪問許可權、關閉其網路埠使得原有的Namenode不能再繼續接受服務請求。使用STONITH技術也可以將之前的主Namenode關機。

最後,HA方案中Namenode的切換對客戶端來說是不可見的,前面已經介紹過,主要通過客戶端庫來完成。

4. 命令列介面

HDFS提供了各種互動方式,例如通過Java API、HTTP、shell命令列的。命令列的互動主要通過hadoop fs來操作。例如:

1、hadoop fs -copyFromLocal // 從本地複製檔案到HDFS
2、hadoop fs mkdir // 建立目錄
3、hadoop fs -ls  // 列出檔案列表

Hadoop中,檔案和目錄的許可權類似於POSIX模型,包括讀、寫、執行3種許可權:

  • 讀許可權(r):用於讀取檔案或者列出目錄中的內容
  • 寫許可權(w):對於檔案,就是檔案的寫許可權。目錄的寫許可權指在該目錄下建立或者刪除檔案(目錄)的許可權。
  • 執行許可權(x):檔案沒有所謂的執行許可權,被忽略。對於目錄,執行許可權用於訪問器目錄下的內容。

每個檔案或目錄都有owner,group,mode三個屬性,owner指檔案的所有者,group為許可權組。mode
由所有者許可權、檔案所屬的組中組員的許可權、非所有者非組員的許可權組成。下圖表示其所有者root擁有讀寫許可權,supergroup組的組員有讀許可權,其他人有讀許可權。

檔案許可權是否開啟通過dfs.permissions.enabled屬性來控制,這個屬性預設為false,沒有開啟安全限制,因此不會對客戶端做授權校驗,如果開啟安全限制,會對操作檔案的使用者做許可權校驗。特殊使用者superuser是Namenode程序的標識,不會針對該使用者做許可權校驗。

最後看一下ls命令的執行結果:

這個返回結果類似於Unix系統下的ls命令,第一欄為檔案的mode,d表示目錄,緊接著3種許可權9位。 第二欄是指檔案的副本數,這個數量通過dfs.replication配置,目錄則使用-表示沒有副本一說。其他諸如所有者、組、更新時間、檔案大小跟Unix系統中的ls命令一致。

如果需要檢視叢集狀態或者瀏覽檔案目錄,可以訪問Namenode暴露的Http Server檢視叢集資訊,一般在namenode所在機器的50070埠。

5. Hadoop檔案系統

前面Hadoop的檔案系統概念是抽象的,HDFS只是其中的一種實現。Hadoop提供的實現如下圖:


簡單介紹一下,Local是對本地檔案系統的抽象,hdfs就是我們最常見的,兩種web形式(webhdfs,swebhdfs)的實現通過HTTP提供檔案操作介面。har是Hadoop體系下的壓縮檔案,檔檔案很多的時候可以壓縮成一個大檔案,可以有效減少元資料的數量。viewfs就是我們前面介紹HDFS Federation張提到的,用來在客戶端遮蔽多個Namenode的底層細節。ftp顧名思義,就是使用ftp協議來實現,對檔案的操作轉化為ftp協議。s3a是對Amazon雲服務提供的儲存系統的實現,azure則是微軟的雲服務平臺實現。

前面我們提到了使用命令列跟HDFS互動,事實上還有很多方式來操作檔案系統。例如Java應用程式可以使用org.apache.hadoop.fs.FileSystem來操作,其他形式的操作也都是基於FileSystem進行封裝。我們這裡主要介紹一下HTTP的互動方式。
WebHDFS和SWebHDFS協議將檔案系統暴露HTTP操作,這種互動方式比原生的Jav客戶端慢,不適合操作大檔案。通過HTTP,有2種訪問方式,直接訪問和通過代理訪問

直接訪問
直接訪問的示意圖如下:

Namenode和Datanode預設打開了嵌入式web server,即dfs.webhdfs.enabled預設為true。webhdfs通過這些伺服器來互動。元資料的操作通過namenode完成,檔案的讀寫首先發到namenode,然後重定向到datanode讀取(寫入)實際的資料流。

通過HDFS代理

採用代理的示意圖如上所示。 使用代理的好處是可以通過代理實現負載均衡或者對頻寬進行限制,或者防火牆設定。代理通過HTTP或者HTTPS暴露為WebHDFS,對應為webhdfs和swebhdfs URL Schema。

代理作為獨立的守護程序,獨立於namenode和datanode,使用httpfs.sh指令碼,預設執行在14000埠

除了FileSystem直接操作,命令列,HTTTP外,還有C語言API,NFS,FUSER等方式,這裡不做過多介紹。

6. Java介面

實際的應用中,對HDFS的大多數操作還是通過FileSystem來操作,這部分重點介紹一下相關的介面,主要關注HDFS的實現類DistributedFileSystem及相關類。

6.1 讀操作

可以使用URL來讀取資料,或者而直接使用FileSystem操作。

從Hadoop URL讀取資料

java.net.URL類提供了資源定位的統一抽象,任何人都可以自己定義一種URL Schema,並提供相應的處理類來進行實際的操作。hdfs schema便是這樣的一種實現。

InputStream in = null;
try {
 in = new URL("hdfs://master/user/hadoop").openStream();
}finally{
 IOUtils.closeStream(in);
}

為了使用自定義的Schema,需要設定URLStreamHandlerFactory,這個操作一個JVM只能進行一次,多次操作會導致不可用,通常在靜態塊中完成。下面的截圖是一個使用示例:

使用FileSystem API讀取資料

1) 首先獲取FileSystem例項,一般使用靜態get工廠方法

public static FileSystem get(Configuration conf) throws IOException
public static FileSystem get(URI uri , Configuration conf) throws IOException
public static FileSystem get(URI uri , Configuration conf,String user) throws IOException

如果是本地檔案,通過getLocal獲取本地檔案系統物件:

public static LocalFileSystem getLocal(COnfiguration conf) thrown IOException

2)呼叫FileSystem的open方法獲取一個輸入流:

public FSDataInputStream open(Path f) throws IOException
public abstarct FSDataInputStream open(Path f , int bufferSize) throws IOException

預設情況下,open使用4KB的Buffer,可以根據需要自行設定。

3)使用FSDataInputStream進行資料操作
FSDataInputStream是java.io.DataInputStream的特殊實現,在其基礎上增加了隨機讀取、部分讀取的能力

public class FSDataInputStream extends DataInputStream
    implements Seekable, PositionedReadable, 
      ByteBufferReadable, HasFileDescriptor, CanSetDropBehind, CanSetReadahead,
      HasEnhancedByteBufferAccess

隨機讀取操作通過Seekable介面定義:

public interface Seekable {
    void seek(long pos) throws IOException;
    long getPos() throws IOException;
}

seek操作開銷昂貴,慎用。

部分讀取通過PositionedReadable介面定義:

public interface PositionedReadable{
    public int read(long pistion ,byte[] buffer,int offser , int length) throws IOException;
    public int readFully(long pistion ,byte[] buffer,int offser , int length) throws IOException;
    public int readFully(long pistion ,byte[] buffer) throws IOException;
}

6.2 寫資料

在HDFS中,檔案使用FileSystem類的create方法及其過載形式來建立,create方法返回一個輸出流FSDataOutputStream,可以呼叫返回輸出流的getPos方法檢視當前檔案的位移,但是不能進行seek操作,HDFS僅支援追加操作。

建立時,可以傳遞一個回撥介面Peofressable,獲取進度資訊

append(Path f)方法用於追加內容到已有檔案,但是並不是所有的實現都提供該方法,例如Amazon的檔案實現就沒有提供追加功能。

下面是一個例子:

String localSrc =  args[0];
String dst = args[1];

InputStream in = new BufferedInputStream(new FileInputStream(localSrc));

COnfiguration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(dst),conf);

OutputStream out = fs.create(new Path(dst), new Progressable(){
    public vid progress(){
        System.out.print(.);
    }
});

IOUtils.copyBytes(in , out, 4096,true);

6.3 目錄操作

使用mkdirs()方法,會自動建立沒有的上級目錄

HDFS中元資料封裝在FileStatus類中,包括長度、block size,replicaions,修改時間、所有者、許可權等資訊。使用FileSystem提供的getFileStatus方法獲取FileStatus。exists()方法判斷檔案或者目錄是否存在;

列出檔案(list),則使用listStatus方法,可以檢視檔案或者目錄的資訊

  public abstract FileStatus[] listStatus(Path f) throws FileNotFoundException, 
                                                         IOException;

Path是個檔案的時候,返回長度為1的陣列。FileUtil提供的stat2Paths方法用於將FileStatus轉化為Path物件。

globStatus則使用萬用字元對檔案路徑進行匹配:

public FileStatus[] globStatus(Path pathPattern) throws IOException

PathFilter用於自定義檔名過濾,不能根據檔案屬性進行過濾,類似於java.io.FileFilter。例如下面這個例子排除到給定正則表示式的檔案:

public interfacePathFilter{
    boolean accept(Path path);
}

6.4 刪除資料

使用FileSystem的delete()方法

public boolean delete(Path f , boolean recursive) throws IOException;

recursive引數在f是個檔案的時候被忽略。如果f是檔案並且recursice為true,則刪除整個目錄,否則丟擲異常.

7. 資料流(讀寫流程)

接下來詳細介紹HDFS讀寫資料的流程,以及一致性模型相關的一些概念。

7.1 讀檔案

大致讀檔案的流程如下:

1)客戶端傳遞一個檔案Path給FileSystem的open方法

2)DFS採用RPC遠端獲取檔案最開始的幾個block的datanode地址。Namenode會根據網路拓撲結構決定返回哪些節點(前提是節點有block副本),如果客戶端本身是Datanode並且節點上剛好有block副本,直接從本地讀取。

3)客戶端使用open方法返回的FSDataInputStream物件讀取資料(呼叫read方法)

4)DFSInputStream(FSDataInputStream實現了改類)連線持有第一個block的、最近的節點,反覆呼叫read方法讀取資料

5)第一個block讀取完畢之後,尋找下一個block的最佳datanode,讀取資料。如果有必要,DFSInputStream會聯絡Namenode獲取下一批Block 的節點資訊(存放於記憶體,不持久化),這些定址過程對客戶端都是不可見的。

6)資料讀取完畢,客戶端呼叫close方法關閉流物件

在讀資料過程中,如果與Datanode的通訊發生錯誤,DFSInputStream物件會嘗試從下一個最佳節點讀取資料,並且記住該失敗節點, 後續Block的讀取不會再連線該節點
讀取一個Block之後,DFSInputStram會進行檢驗和驗證,如果Block損壞,嘗試從其他節點讀取資料,並且將損壞的block彙報給Namenode。
客戶端連線哪個datanode獲取資料,是由namenode來指導的,這樣可以支援大量併發的客戶端請求,namenode儘可能將流量均勻分佈到整個叢集。
Block的位置資訊是儲存在namenode的記憶體中,因此相應位置請求非常高效,不會成為瓶頸。

7.2 寫檔案

步驟分解
1)客戶端呼叫DistributedFileSystem的create方法

2)DistributedFileSystem遠端RPC呼叫Namenode在檔案系統的名稱空間中建立一個新檔案,此時該檔案沒有關聯到任何block。 這個過程中,Namenode會做很多校驗工作,例如是否已經存在同名檔案,是否有許可權,如果驗證通過,返回一個FSDataOutputStream物件。 如果驗證不通過,丟擲異常到客戶端。

3)客戶端寫入資料的時候,DFSOutputStream分解為packets,並寫入到一個數據佇列中,該佇列由DataStreamer消費。

4)DateStreamer負責請求Namenode分配新的block存放的資料節點。這些節點存放同一個Block的副本,構成一個管道。 DataStreamer將packer寫入到管道的第一個節點,第一個節點存放好packer之後,轉發給下一個節點,下一個節點存放 之後繼續往下傳遞。

5)DFSOutputStream同時維護一個ack queue佇列,等待來自datanode確認訊息。當管道上的所有datanode都確認之後,packer從ack佇列中移除。

6)資料寫入完畢,客戶端close輸出流。將所有的packet重新整理到管道中,然後安心等待來自datanode的確認訊息。全部得到確認之後告知Namenode檔案是完整的。 Namenode此時已經知道檔案的所有Block資訊(因為DataStreamer是請求Namenode分配block的),只需等待達到最小副本數要求,然後返回成功資訊給客戶端。

Namenode如何決定副本存在哪個Datanode?

HDFS的副本的存放策略是可靠性、寫頻寬、讀頻寬之間的權衡。預設策略如下:

  • 第一個副本放在客戶端相同的機器上,如果機器在叢集之外,隨機選擇一個(但是會盡可能選擇容量不是太慢或者當前操作太繁忙的)
  • 第二個副本隨機放在不同於第一個副本的機架上。
  • 第三個副本放在跟第二個副本同一機架上,但是不同的節點上,滿足條件的節點中隨機選擇。
  • 更多的副本在整個叢集上隨機選擇,雖然會盡量便面太多副本在同一機架上。
    副本的位置確定之後,在建立寫入管道的時候,會考慮網路拓撲結構。下面是可能的一個存放策略:

這樣選擇很好滴平衡了可靠性、讀寫效能

  • 可靠性:Block分佈在兩個機架上
  • 寫頻寬:寫入管道的過程只需要跨越一個交換機
  • 讀頻寬:可以從兩個機架中任選一個讀取

7.3 一致性模型

一致性模型描述檔案系統中讀寫操縱的可見性。HDFS中,檔案一旦建立之後,在檔案系統的名稱空間中可見:

Path p = new Path("p");
fs.create(p);
assertTaht(fs.exists(p),is(true));

但是任何被寫入到檔案的內容不保證可見,即使物件流已經被重新整理。
“`java
Path p = new Path(“p”);
OutputStream out = fs.create(p);
out.write(“content”.getBytes(“UTF-8”));
out.flush();
assertTaht(fs.getFileStatus(p).getLen,0L); // 為0,即使呼叫了flush


如果需要強制重新整理資料到Datanode,使用FSDataOutputStream的hflush方法強制將緩衝刷到datanode
hflush之後,HDFS保證到這個時間點為止寫入到檔案的資料都到達所有的資料節點。
 ```java
Path p = new Path("p");
OutputStream out = fs.create(p);
out.write("content".getBytes("UTF-8"));
out.flush();
assertTaht(fs.getFileStatus(p).getLen,is(((long,"content".length())));

關閉物件流時,內部會呼叫hflush方法,但是hflush不保證datanode資料已經寫入到磁碟,只是保證寫入到datanode的記憶體, 因此在機器斷電的時候可能導致資料丟失,如果要保證寫入磁碟,使用hsync方法,hsync型別與fsync()的系統呼叫,fsync提交某個檔案控制代碼的緩衝資料。

FileOutputStreamout = new FileOutPutStream(localFile);
out.write("content".getBytes("UTF-8"));
out.flush();
out.getFD().sync();
assertTaht(localFile.getLen,is(((long,"content".length())));

使用hflush或hsync會導致吞吐量下降,因此設計應用時,需要在吞吐量以及資料的健壯性之間做權衡。

另外,檔案寫入過程中,當前正在寫入的Block對其他Reader不可見。

7.4 Hadoop節點距離

在讀取和寫入的過程中,namenode在分配Datanode的時候,會考慮節點之間的距離。HDFS中,距離沒有
採用頻寬來衡量,因為實際中很難準確度量兩臺機器之間的頻寬。
Hadoop把機器之間的拓撲結構組織成樹結構,並且用到達公共父節點所需跳轉數之和作為距離。事實上這是一個距離矩陣的例子。下面的例子簡明地說明了距離的計算:

Hadoop叢集的拓撲結構需要手動配置,如果沒配置,Hadoop預設所有節點位於同一個資料中心的同一機架上。

8 相關運維工具

8.1 使用distcp並行複製

前面的關注點都在於單執行緒的訪問,如果需要並行處理檔案,需要自己編寫應用。Hadoop提供的distcp工具用於並行匯入資料到Hadoop或者從Hadoop匯出。一些例子:

hadoop distcp file1 file2  //可以作為fs -cp命令的高效替代
hadoop distcp dir1 dir2
hadoop distcp -update dir1 dir2 #update引數表示只同步被更新的檔案,其他保持不變

distcp是底層使用MapReduce實現,只有map實現,沒有reduce。在map中並行複製檔案。 distcp儘可能在map之間平均分配檔案。map的數量可以通過-m引數指定:

hadoop distcp -update -delete -p hdfs://master1:9000/foo hdfs://master2/foo 

這樣的操作常用於在兩個叢集之間複製資料,update引數表示只同步被更新過的資料,delete會刪除目標目錄中存在,但是源目錄不存在的檔案。p引數表示保留檔案的全校、block大小、副本數量等屬性。

如果兩個叢集的Hadoop版本不相容,可以使用webhdfs協議:

hadoop distcp webhdfs: //namenode1: 50070/foo webhdfs: //namenode2: 50070/foo

8.2 平衡HDFS叢集

在distcp工具中,如果我們指定map數量為1,不僅速度很慢,每個Block第一個副本將全部落到執行這個唯一map的節點上,直到磁碟溢位。因此使用distcp的時候,最好使用預設的map數量,即20.
HDFS在Block均勻分佈在各個節點上的時候工作得最好,如果沒有辦法在作業中儘量保持叢集平衡,例如為了限制map數量(以便其他節點可以被別的作業使用),那麼可以使用balancer工具來調整叢集的Block分佈。

參考

主要參考《Hadoop》權威指南第3章,自己進一步整理。感受原作者提供這麼好的書籍。