HBase存儲架構

轉自:http://blog.csdn.net/trend_cdc_spn/article/details/5755620

 

HBase最隱祕的問題之一就是它的數據是如何存儲的。雖然大多數用戶都不會因爲這個問題向你抱怨,但是如果你想學習哪些高級的配置選項並瞭解它們的意思,你可能就需要來了解一下這個存儲問題了。“怎樣才能把HBase調整到最適合我需求的狀態?”你可能對於這樣一系列類似的問題非常感興趣。那麼你就需要繞過這些問題來學習HBase的基礎知識。另一個支持你學習這些基礎知識的理由是有時候各種各樣你想不到的災難需要你恢復整個HBase。

我首先學習了HBase中控制各種不同文件的獨立的類,然後根據我對整個HBase存儲系統的理解在腦海中構建HBase架構的圖像。但是我發現想要在頭腦中構建出一幅連貫的HBase架構圖片很困難,於是我就把它畫了出來。

你可能注意到了這不是一個UML圖或者調用圖。這個圖混合了類和他們管理控制的文件,將注意力集中到了本文要討論的主題上。後面我將會討論這張圖所涉及到的細節,包括一些配置文件項是如何影響底層的文件存儲系統的。

好,我們現在來分析這張圖裏面到底包含了什麼。首先HBase操控兩種基本類型的文件,一種用於存儲WAL的log,另一種用於存儲具體的數據。這兩種文件主要由HRegionServer來管理,但是在有的情況下HMaster會跳過HRegionServer,直接操作這兩種文件。你可能注意到了,這些文件都被存儲在HDFS上面,並且每個文件包含了多個數據塊。配置文件中就有一個選項用來調整系統控制數據的大小。我們後面會詳細討論這個問題。

接下來看一下數據的大致流程。假設你需要通過某個特定的RowKey查詢一行記錄,首先Client端會連接Zookeeper Qurom,通過Zookeeper,Client能獲知哪個Server管理-ROOT- Region。接着Client訪問管理-ROOT-的Server,進而獲知哪個Server管理.META.表。這兩個信息Client只會獲取一次並緩存起來。在後續的操作中Client會直接訪問管理.META.表的Server,並獲取Region分佈的信息。一旦Client獲取了這一行的位置信息,比如這一行屬於哪個Region,Client將會緩存這個信息並直接訪問HRegionServer。久而久之Client緩存的信息漸漸增多,即使不訪問.META.表也能知道去訪問哪個HRegionServer。

注意:當HBase啓動的時候HMaster負責分配Region給HRegionServer,這其中當然也包括-ROOT-表和.META.表的Region。

 

接下來HRegionServer打開這個Region並創建一個HRegion對象。當HRegion打開以後,它給每個table的每個HColumnFamily創建一個Store實例。每個Store實例擁有一個或者多個StoreFile實例。StoreFile對HFile做了輕量級的包裝。除了Store實例以外,每個HRegion還擁有一個MemStore實例和一個HLog實例。現在我們就可以看看這些實例是如何在一起工作的,遵循什麼樣的規則以及這些規則的例外。


保留住Put

現在看一下數據是怎樣被寫到實際的存儲中去的。Client發起了一個HTable.put(Put)請求給HRegionServer,HRegionServer會將請求匹配到某個具體的HRegion上面。緊接着的操作時決定是否寫WAL log。是否寫WAL log由Client傳遞的一個標誌決定,你可以設置這個標誌:Put.writeToWAL(boolean)。WAL log文件是一個標準的Hadoop SequenceFile(現在還在討論是否應該把文件格式改成一個更適合HBase的格式)。在文件中存儲了HLogKey,這些Keys包含了和實際數據對應的序列號,用途是當RegionServer崩潰以後能將WAL log中的數據同步到永久存儲中去。做完這一步以後,Put數據會被保存到MemStore中,同時會檢查MemStore是否已經滿了,如果已經滿了,則會觸發一個Flush to Disk的請求。HRegionServer有一個獨立的線程來處理Flush to Disk的請求,它負責將數據寫成HFile文件並存到HDFS上。它也會存儲最後寫入的數據序列號,這樣就可以知道哪些數據已經存入了永久存儲的HDFS中。現在讓我們來看看這些存儲文件。

 

存儲文件

HBase在HDFS上面的所有文件有一個可配置的根目錄,默認根目錄是/hbase。通過使用hadoop的DFS工具就可以看到這些文件夾的結構。

在根目錄下面你可以看到一個.logs文件夾,這裏面存了所有由HLog管理的WAL log文件。在.logs目錄下的每個文件夾對應一個HRegionServer,每個HRegionServer下面的每個log文件對應一個Region。

有時候你會發現一些oldlogfile.log文件(在大多數情況下你可能看不到這個文件),這個文件在一種異常情況下會被產生。這個異常情況就是HMaster對log文件的訪問情況產生了懷疑,它會產生一種稱作“log splits”的結果。有時候HMaster會發現某個log文件沒人管了,就是說任何一個HRegionServer都不會管理這個log文件(有可能是原來管理這個文件的HRegionServer掛了),HMaster會負責分割這個log文件(按照它們歸屬的Region),並把那些HLogKey寫到一個叫做oldlogfile.log的文件中,並按照它們歸屬的Region直接將文件放到各自的Region文件夾下面。各個HRegion會從這個文件中讀取數據並將它們寫入到MemStore中去,並開始將數據Flush to Disk。然後就可以把這個oldlogfile.log文件刪除了。

注意:有時候你可能會發現另一個叫做oldlogfile.log.old的文件,這是由於HMaster做了重複分割log文件的操作並發現oldlogfile.log已經存在了。這時候就需要和HRegionServer以及HMaster協商到底發生了什麼,以及是否可以把old的文件刪掉了。從我目前遇到的情況來看,old文件都是空的並且可以被安全刪除的。

HBase的每個Table在根目錄下面用一個文件夾來存儲,文件夾的名字就是Table的名字。在Table文件夾下面每個Region也用一個文件夾來存儲,但是文件夾的名字並不是Region的名字,而是Region的名字通過Jenkins Hash計算所得到的字符串。這樣做的原因是Region的名字裏面可能包含了不能在HDFS裏面作爲路徑名的字符。在每個Region文件夾下面每個ColumnFamily也有自己的文件夾,在每個ColumnFamily文件夾下面就是一個個HFile文件了。所以整個文件夾結構看起來應該是這個樣子的:

/hbase/<tablename>/<encoded-regionname>/<column-family>/<filename>

在每個Region文件夾下面你會發現一個.regioninfo文件,這個文件用來存儲這個Region的Meta Data。通過這些Meta Data我們可以重建被破壞的.META.表,關於.regioninfo的應用你可以參考HBASE-7和HBASE-1867。

有一件事情前面一直沒有提到,那就是Region的分割。當一個Region的數據文件不斷增長並超過一個最大值的時候(你可以配置這個最大值 hbase.hregion.max.filesize),這個Region會被切分成兩個。這個過程完成的非常快,因爲原始的數據文件並不會被改變,系統只是簡單的創建兩個Reference文件指向原始的數據文件。每個Reference文件管理原始文件一半的數據。Reference文件名字是一個ID,它使用被參考的Region的名字的Hash作爲前綴。例如:1278437856009925445.3323223323。Reference文件只含有非常少量的信息,這些信息包括被分割的原始Region的Key以及這個文件管理前半段還是後半段。HBase使用HalfHFileReader類來訪問Reference文件並從原始數據文件中讀取數據。前面的架構圖只並沒有畫出這個類,因爲它只是臨時使用的。只有當系統做Compaction的時候原始數據文件纔會被分割成兩個獨立的文件並放到相應的Region目錄下面,同時原始數據文件和那些Reference文件也會被清除。

前面dump出來的文件結構也證實了這個過程,在每個Table的目錄下面你可以看到一個叫做compaction.dir的目錄。這個文件夾是一個數據交換區,用於存放split和compact Region過程中生成的臨時數據。

 

HFile

現在我們將深入HBase存儲架構的核心,探討HBase具體的數據存儲文件的結構。HFile就是這個數據存儲文件的結構(Ryan Rawson就是靠它揚名立萬的)。創建HFile這樣一個文件結構的目的只有一個:快速高效的存儲HBase的數據。HFile是基於Hadoop TFile的(參見 HADOOP-3315)。HFile模仿了Google Bigtable中SSTable的格式。原先HBase使用Hadoop的MapFile,但是這種文件已經被證明了效率差。

現在讓我們來看看這個文件結構到底是什麼樣的。

 

 

 

首先這個文件是不定長的,長度固定的只有其中的兩塊:Trailer和FileInfo。正如圖中所示的,Trailer中有指針指向其他數據塊的起始點。Index數據塊記錄了每個Data塊和Meta塊的起始點。Data塊和Meta塊都是可有可無的,但是對於大部分的HFile,你都可以看到Data塊。

那麼每個塊的大小是如何確定的呢?這個值可以在創建一個Table的時候通過HColumnDescriptor(實際上應該稱作FamilyDescriptor)來設定。這裏我們可以看一個例子:


{NAME => 'docs', FAMILIES => [{NAME => 'cache', COMPRESSION => 'NONE', VERSIONS => '3', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'false'}, {NAME => 'contents', COMPRESSION => 'NONE', VERSIONS => '3', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'false'}, ...

 

從這裏可以看出這是一個叫做docs的Table,它有兩個Family:cache和contents,這兩個Family對應的HFile的數據塊的大小都是64K。

關於如何設定數據塊的大小,我們應用一段HFile源碼中的註釋:

我們推薦將數據塊的大小設置爲8KB至1MB。大的數據塊比較適合順序的查詢(比如Scan),但不適合隨機查詢,想想看,每一次隨機查詢可能都需要你去解壓縮一個大的數據塊。小的數據塊適合隨機的查詢,但是需要更多的內存來保存數據塊的索引(Data Index),而且創建文件的時候也可能比較慢,因爲在每個數據塊的結尾我們都要把壓縮的數據流Flush到文件中去(引起更多的Flush操作)。並且由於壓縮器內部還需要一定的緩存,最小的數據塊大小應該在20KB - 30KB左右。可能從前面的描述你會發現數據塊(Data Block)是數據壓縮的一個單位。後面我們會深入Data Block內部去了解它的詳細構造。

在HBase的配置文件中你會看到一個參數:hfile.min.blocksize.size,這個參數看上去只會用在數據遷移或者通過工具直接創建HFile的過程中。(貌似HBase創建HFile不會使用這個參數,HBase使用的是.META.表中記錄的那個值)。

呼——,到現在爲止解釋的還不錯吧。好了,讓我們繼續。有時候我們可能會想知道一個HFile是否正常以及它裏面包含了什麼內容。沒問題,已經有一個應用程序來做這件事了。

HFile.main()本身就提供了一個用來dump HFile的工具。

 

 

這裏有一個dump文件的例子:

 

 

 

第一部分是存儲具體數據的KeyValue對,每個數據塊除了開頭的Magic以外就是一個個KeyValue對拼接而成。後面會詳細介紹每個KeyValue對的內部構造。第二部分是Tailer塊的具體內容,最後一部分是FileInfo塊的具體內容。Dump HFile還有一個作用就是檢查HFile是否正常。

 

KeyValue對

HFile裏面的每個KeyValue對就是一個簡單的byte數組。但是這個byte數組裏麪包含了很多項,並且有固定的結構。我們來看看裏面的具體結構:

 

開始是兩個固定長度的數值,分別表示Key的長度和Value的長度。緊接着是Key,開始是固定長度的數值,表示RowKey的長度,緊接着是RowKey,然後是固定長度的數值,表示Family的長度,然後是Family,接着是Qualifier,然後是兩個固定長度的數值,表示Time Stamp和Key Type。Value部分沒有這麼複雜的結構,就是純粹的數據。

java.org.apache.hadoop.hbase.KeyValue是用來處理這個KeyValue,你可能會發現在這個類裏面有兩個方法:getKey和getRow。getKey當然是用來獲取Key的內容,那麼getRow是什麼?其實是用來獲取RowKey的。RowKey不是HBase的基本元素嗎?是的,這個類已經不是純粹的Key&Value處理,它已經打上了HBase的烙印。

好了,這篇文章就到此爲止了,它對HBase的存儲架構做了一個大致的介紹。希望這篇文章對於那些想更深入的挖掘HBase細節的人來說,能作爲一個起點

發佈了35 篇原創文章 · 獲贊 4 · 訪問量 30萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章