目錄
正文
這張圖是有一個錯誤點:應該是每一個 RegionServer 就只有一個 HLog,而不是一個 Region 有一個 HLog。
從HBase的架構圖上可以看出,HBase中的組件包括Client、Zookeeper、HMaster、HRegionServer、HRegion、Store、MemStore、StoreFile、HFile、HLog等,接下來介紹他們的作用。
1、HBase 有兩張?zhí)厥獗恚?/p>
.META.:記錄了用戶所有表拆分出來的的 Region 映射信息,.META.可以有多個 Regoin
-ROOT-:記錄了.META.表的 Region 信息,-ROOT-只有一個 Region,無論如何不會分裂
2、Client 訪問用戶數(shù)據(jù)前需要首先訪問 ZooKeeper,找到-ROOT-表的 Region 所在的位置,然 后訪問-ROOT-表,接著訪問.META.表,最后才能找到用戶數(shù)據(jù)的位置去訪問,中間需要多次 網(wǎng)絡操作,不過 client 端會做 cache 緩存。
1、ZooKeeper 為 HBase 提供 Failover 機制,選舉 Master,避免單點 Master 單點故障問題
2、存儲所有 Region 的尋址入口:-ROOT-表在哪臺服務器上。-ROOT-這張表的位置信息
3、實時監(jiān)控 RegionServer 的狀態(tài),將 RegionServer 的上線和下線信息實時通知給 Master
4、存儲 HBase 的 Schema,包括有哪些 Table,每個 Table 有哪些 Column Family
1、為 RegionServer 分配 Region
2、負責 RegionServer 的負載均衡
3、發(fā)現(xiàn)失效的 RegionServer 并重新分配其上的 Region
4、HDFS 上的垃圾文件(HBase)回收
5、處理 Schema 更新請求(表的創(chuàng)建,刪除,修改,列簇的增加等等)
1、RegionServer 維護 Master 分配給它的 Region,處理對這些 Region 的 IO 請求
2、RegionServer 負責 Split 在運行過程中變得過大的 Region,負責 Compact 操作
可以看到,client 訪問 HBase 上數(shù)據(jù)的過程并不需要 master 參與(尋址訪問 zookeeper 和 RegioneServer,數(shù)據(jù)讀寫訪問 RegioneServer),Master 僅僅維護者 Table 和 Region 的元數(shù)據(jù)信息,負載很低。
.META. 存的是所有的 Region 的位置信息,那么 RegioneServer 當中 Region 在進行分裂之后 的新產(chǎn)生的 Region,是由 Master 來決定發(fā)到哪個 RegioneServer,這就意味著,只有 Master 知道 new Region 的位置信息,所以,由 Master 來管理.META.這個表當中的數(shù)據(jù)的 CRUD
所以結合以上兩點表明,在沒有 Region 分裂的情況,Master 宕機一段時間是可以忍受的。
table在行的方向上分隔為多個Region。Region是HBase中分布式存儲和負載均衡的最小單元,即不同的region可以分別在不同的Region Server上,但同一個Region是不會拆分到多個server上。
Region按大小分隔,每個表一般是只有一個region。隨著數(shù)據(jù)不斷插入表,region不斷增大,當region的某個列族達到一個閾值時就會分成兩個新的region。
每個region由以下信息標識:< 表名,startRowkey,創(chuàng)建時間>
由目錄表(-ROOT-和.META.)記錄該region的endRowkey
每一個region由一個或多個store組成,至少是一個store,hbase會把一起訪問的數(shù)據(jù)放在一個store里面,即為每個 ColumnFamily建一個store,如果有幾個ColumnFamily,也就有幾個Store。一個Store由一個memStore和0或者 多個StoreFile組成。 HBase以store的大小來判斷是否需要切分region
memStore 是放在內存里的。保存修改的數(shù)據(jù)即keyValues。當memStore的大小達到一個閥值(默認128MB)時,memStore會被flush到文 件,即生成一個快照。目前hbase 會有一個線程來負責memStore的flush操作。
memStore內存中的數(shù)據(jù)寫到文件后就是StoreFile,StoreFile底層是以HFile的格式保存。
HBase中KeyValue數(shù)據(jù)的存儲格式,HFile是Hadoop的 二進制格式文件,實際上StoreFile就是對Hfile做了輕量級包裝,即StoreFile底層就是HFile
HLog(WAL log):WAL意為write ahead log,用來做災難恢復使用,HLog記錄數(shù)據(jù)的所有變更,一旦region server 宕機,就可以從log中進行恢復。
HLog文件就是一個普通的Hadoop Sequence File, Sequence File的value是key時HLogKey對象,其中記錄了寫入數(shù)據(jù)的歸屬信息,除了table和region名字外,還同時包括sequence number和timestamp,timestamp是寫入時間,sequence number的起始值為0,或者是最近一次存入文件系統(tǒng)中的sequence number。 Sequence File的value是HBase的KeyValue對象,即對應HFile中的KeyValue。
1、Table 中的所有行都按照 RowKsey 的字典序排列。
2、Table 在行的方向上分割為多個 HRegion。
3、HRegion 按大小分割的(默認 10G),每個表一開始只有一個 HRegion,隨著數(shù)據(jù)不斷插入 表,HRegion 不斷增大,當增大到一個閥值的時候,HRegion 就會等分會兩個新的 HRegion。 當表中的行不斷增多,就會有越來越多的 HRegion。
4、HRegion 是 Hbase 中分布式存儲和負載均衡的最小單元。最小單元就表示不同的 HRegion 可以分布在不同的 HRegionserver 上。但一個 HRegion 是不會拆分到多個 server 上的。
5、HRegion 雖然是負載均衡的最小單元,但并不是物理存儲的最小單元。事實上,HRegion 由一個或者多個 Store 組成,每個 Store 保存一個 Column Family。每個 Strore 又由一個 memStore 和 0 至多個 StoreFile 組成
StoreFile 以 HFile 格式保存在 HDFS 上,請看下圖 HFile 的數(shù)據(jù)組織格式:
首先 HFile 文件是不定長的,長度固定的只有其中的兩塊:Trailer 和 FileInfo。
正如圖中所示:
Trailer 中有指針指向其他數(shù)據(jù)塊的起始點。
FileInfo 中記錄了文件的一些 Meta 信息,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY 等。
HFile 分為六個部分:
Data Block 段–保存表中的數(shù)據(jù),這部分可以被壓縮
Meta Block 段 (可選的)–保存用戶自定義的 kv 對,可以被壓縮。
File Info 段–Hfile 的元信息,不被壓縮,用戶也可以在這一部分添加自己的元信息。
Data Block Index 段–Data Block 的索引。每條索引的 key 是被索引的 block 的第一條記錄的 key。
Meta Block Index 段 (可選的)–Meta Block 的索引。
Trailer 段–這一段是定長的。保存了每一段的偏移量,讀取一個 HFile 時,會首先讀取 Trailer, Trailer保存了每個段的起始位置(段的Magic Number用來做安全check),然后,DataBlock Index 會被讀取到內存中,這樣,當檢索某個 key 時,不需要掃描整個 HFile,而只需從內存中找 到key所在的block,通過一次磁盤io將整個block讀取到內存中,再找到需要的key。DataBlock Index 采用 LRU 機制淘汰。
HFile 的 Data Block,Meta Block 通常采用壓縮方式存儲,壓縮之后可以大大減少網(wǎng)絡 IO 和磁 盤 IO,隨之而來的開銷當然是需要花費 cpu 進行壓縮和解壓縮。
目標 Hfile 的壓縮支持兩種方式:Gzip,LZO。
Data Index 和 Meta Index 塊記錄了每個 Data 塊和 Meta 塊的起始點。
Data Block 是 HBase I/O 的基本單元,為了提高效率,HRegionServer 中有基于 LRU 的 Block Cache 機制。每個 Data 塊的大小可以在創(chuàng)建一個 Table 的時候通過參數(shù)指定,大號的 Block 有利于順序 Scan,小號 Block 利于隨機查詢。 每個 Data 塊除了開頭的 Magic 以外就是一個 個 KeyValue 對拼接而成, Magic 內容就是一些隨機數(shù)字,目的是防止數(shù)據(jù)損壞。
HFile 里面的每個 KeyValue 對就是一個簡單的 byte 數(shù)組。但是這個 byte 數(shù)組里面包含了很 多項,并且有固定的結構。我們來看看里面的具體結構:
開始是兩個固定長度的數(shù)值,分別表示 Key 的長度和 Value 的長度。緊接著是 Key,開始是 固定長度的數(shù)值,表示 RowKey 的長度,緊接著是 RowKey,然后是固定長度的數(shù)值,表示 Family 的長度,然后是 Family,接著是 Qualifier,然后是兩個固定長度的數(shù)值,表示 Time Stamp 和 Key Type(Put/Delete)。Value 部分沒有這么復雜的結構,就是純粹的二進制數(shù)據(jù)了。
一個 Hregion 由多個 Store 組成,每個 Store 包含一個列族的所有數(shù)據(jù)。
Store 包括位于內存的一個 memstore 和位于硬盤的多個 storefile 組成。
寫操作先寫入 memstore,當 memstore 中的數(shù)據(jù)量達到某個閾值,HRegionServer 啟動 flushcache 進程寫入 storefile,每次寫入形成單獨一個 Hfile。
當總 storefile 大小超過一定閾值后,會把當前的 region 分割成兩個,并由 HMaster 分配給相 應的 region 服務器,實現(xiàn)負載均衡。
客戶端檢索數(shù)據(jù)時,先在 memstore 找,找不到再找 storefile。
WAL 意為 Write ahead log(http://en.wikipedia.org/wiki/Write-ahead_logging),類似 mysql 中的 binlog,用來做災難恢復之用,Hlog 記錄數(shù)據(jù)的所有變更,一旦數(shù)據(jù)修改,就可以從 log 中 進行恢復。
每個 Region Server 維護一個 Hlog,而不是每個 Region 一個。這樣不同 region(來自不同 table) 的日志會混在一起,這樣做的目的是不斷追加單個文件相對于同時寫多個文件而言,可以減 少磁盤尋址次數(shù),因此可以提高對 table 的寫性能。帶來的麻煩是,如果一臺 region server 下線,為了恢復其上的 region,需要將 region server 上的 log 進行拆分,然后分發(fā)到其它 region server 上進行恢復。
HLog 文件就是一個普通的 Hadoop Sequence File(序列化文件):
1、HLog Sequence File 的 Key 是 HLogKey 對象,HLogKey 中記錄了寫入數(shù)據(jù)的歸屬信息,除 了 table 和 region 名字外,同時還包括 sequence number 和 timestamp,timestamp 是”寫入 時間”,sequence number 的起始值為 0,或者是最近一次存入文件系統(tǒng)中 sequence number。
2、HLog Sequece File 的 Value 是 HBase 的 KeyValue 對象,即對應 HFile 中的 KeyValue。
既然讀寫都在 RegionServer 上發(fā)生,我們前面有講到,每個 RegionSever 為一定數(shù)量的 Region 服務,那么 Client 要對某一行數(shù)據(jù)做讀寫的時候如何能知道具體要去訪問哪個 RegionServer 呢?那就是接下來我們要討論的問題
在 HBase-0.96 版本以前,HBase 有兩個特殊的表,分別是-ROOT-表和.META.表,其中-ROOT的位置存儲在 ZooKeeper 中,-ROOT-本身存儲了.META. Table 的 RegionInfo 信息,并且-ROOT不會分裂,只有一個 Region。而.META.表可以被切分成多個 Region。讀取的流程如下圖所示:
詳細步驟:
第 1 步:Client 請求 ZooKeeper 獲得-ROOT-所在的 RegionServer 地址
第 2 步:Client 請求-ROOT-所在的 RS 地址,獲取.META.表的地址,Client 會將-ROOT-的相關 信息 cache 下來,以便下一次快速訪問
第 3 步:Client 請求.META.表的 RegionServer 地址,獲取訪問數(shù)據(jù)所在 RegionServer 的地址, Client 會將.META.的相關信息 cache 下來,以便下一次快速訪問
第 4 步:Client 請求訪問數(shù)據(jù)所在 RegionServer 的地址,獲取對應的數(shù)據(jù)
從上面的路徑我們可以看出,用戶需要 3 次請求才能直到用戶 Table 真正的位置,這在一定 程序帶來了性能的下降。在 0.96 之前使用 3 層設計的主要原因是考慮到元數(shù)據(jù)可能需要很 大。但是真正集群運行,元數(shù)據(jù)的大小其實很容易計算出來。在 BigTable 的論文中,每行 METADATA 數(shù)據(jù)存儲大小為 1KB 左右,如果按照一個 Region 為 128M 的計算,3 層設計可以支持的 Region 個數(shù)為 2^34 個,采用 2 層設計可以支持 2^17(131072)。那么 2 層設計的情 況下一個集群可以存儲 4P 的數(shù)據(jù)。這僅僅是一個 Region 只有 128M 的情況下。如果是 10G 呢? 因此,通過計算,其實 2 層設計就可以滿足集群的需求。因此在 0.96 版本以后就去掉 了-ROOT-表了。
如上面的計算,2 層結構其實完全能滿足業(yè)務的需求,因此 0.96 版本以后將-ROOT-表去掉了。 如下圖所示:
訪問路徑變成了 3 步:
第 1 步:Client 請求 ZooKeeper 獲取.META.所在的 RegionServer 的地址。
第 2 步:Client 請求.META.所在的 RegionServer 獲取訪問數(shù)據(jù)所在的 RegionServer 地址,Client 會將.META.的相關信息 cache 下來,以便下一次快速訪問。
第 3 步:Client 請求數(shù)據(jù)所在的 RegionServer,獲取所需要的數(shù)據(jù)。
總結去掉-ROOT-的原因有如下 2 點:
其一:提高性能
其二:2 層結構已經(jīng)足以滿足集群的需求
這里還有一個問題需要說明,那就是 Client 會緩存.META.的數(shù)據(jù),用來加快訪問,既然有緩 存,那它什么時候更新?如果.META.更新了,比如 Region1 不在 RerverServer2 上了,被轉移 到了 RerverServer3 上。Client 的緩存沒有更新會有什么情況?
其實,Client 的元數(shù)據(jù)緩存不更新,當.META.的數(shù)據(jù)發(fā)生更新。如上面的例子,由于 Region1 的位置發(fā)生了變化,Client 再次根據(jù)緩存去訪問的時候,會出現(xiàn)錯誤,當出現(xiàn)異常達到重試 次數(shù)后就會去.META.所在的 RegionServer 獲取最新的數(shù)據(jù),如果.META.所在的 RegionServer 也變了,Client 就會去 ZooKeeper 上獲取.META.所在的 RegionServer 的最新地址。
1、客戶端通過 ZooKeeper 以及-ROOT-表和.META.表找到目標數(shù)據(jù)所在的 RegionServer(就是 數(shù)據(jù)所在的 Region 的主機地址)
2、聯(lián)系 RegionServer 查詢目標數(shù)據(jù)
3、RegionServer 定位到目標數(shù)據(jù)所在的 Region,發(fā)出查詢請求
4、Region 先在 Memstore 中查找,命中則返回
5、如果在 Memstore 中找不到,則在 Storefile 中掃描 為了能快速的判斷要查詢的數(shù)據(jù)在不在這個 StoreFile 中,應用了 BloomFilter
(BloomFilter,布隆過濾器:迅速判斷一個元素是不是在一個龐大的集合內,但是他有一個 弱點:它有一定的誤判率)
(誤判率:原本不存在與該集合的元素,布隆過濾器有可能會判斷說它存在,但是,如果 布隆過濾器,判斷說某一個元素不存在該集合,那么該元素就一定不在該集合內)
1、Client 先根據(jù) RowKey 找到對應的 Region 所在的 RegionServer
2、Client 向 RegionServer 提交寫請求
3、RegionServer 找到目標 Region
4、Region 檢查數(shù)據(jù)是否與 Schema 一致
5、如果客戶端沒有指定版本,則獲取當前系統(tǒng)時間作為數(shù)據(jù)版本
6、將更新寫入 WAL Log
7、將更新寫入 Memstore
8、判斷 Memstore 的是否需要 flush 為 StoreFile 文件。
Hbase 在做數(shù)據(jù)插入操作時,首先要找到 RowKey 所對應的的 Region,怎么找到的?其實這 個簡單,因為.META.表存儲了每張表每個 Region 的起始 RowKey 了。
建議:在做海量數(shù)據(jù)的插入操作,避免出現(xiàn)遞增 rowkey 的 put 操作
如果 put 操作的所有 RowKey 都是遞增的,那么試想,當插入一部分數(shù)據(jù)的時候剛好進行分 裂,那么之后的所有數(shù)據(jù)都開始往分裂后的第二個 Region 插入,就造成了數(shù)據(jù)熱點現(xiàn)象。
細節(jié)描述:
HBase 使用 MemStore 和 StoreFile 存儲對表的更新。
數(shù)據(jù)在更新時首先寫入 HLog(WAL Log),再寫入內存(MemStore)中,MemStore 中的數(shù)據(jù)是排 序的,當 MemStore 累計到一定閾值時,就會創(chuàng)建一個新的 MemStore,并且將老的 MemStore 添加到 flush 隊列,由單獨的線程 flush 到磁盤上,成為一個 StoreFile。于此同時,系統(tǒng)會在 ZooKeeper 中記錄一個 redo point,表示這個時刻之前的變更已經(jīng)持久化了。當系統(tǒng)出現(xiàn)意外時,可能導致內存(MemStore)中的數(shù)據(jù)丟失,此時使用 HLog(WAL Log)來恢復 checkpoint 之后的數(shù)據(jù)。
StoreFile 是只讀的,一旦創(chuàng)建后就不可以再修改。因此 HBase 的更新/修改其實是不斷追加 的操作。當一個 Store 中的 StoreFile 達到一定的閾值后,就會進行一次合并(minor_compact, major_compact),將對同一個 key 的修改合并到一起,形成一個大的 StoreFile,當 StoreFile 的大小達到一定閾值后,又會對 StoreFile 進行 split,等分為兩個 StoreFile。由于對表的更 新是不斷追加的,compact 時,需要訪問 Store 中全部的 StoreFile 和 MemStore,將他們按 rowkey 進行合并,由于 StoreFile 和 MemStore 都是經(jīng)過排序的,并且 StoreFile 帶有內存中 索引,合并的過程還是比較快。
major_compact 和 minor_compact 的區(qū)別:
minor_compact 僅僅合并小文件(HFile)
major_compact 合并一個 region 內的所有文件
Client 寫入 -> 存入 MemStore,一直到 MemStore 滿 -> Flush 成一個 StoreFile,直至增長到 一定閾值 -> 觸發(fā) Compact 合并操作 -> 多個 StoreFile 合并成一個 StoreFile,同時進行版本 合并和數(shù)據(jù)刪除 -> 當 StoreFiles Compact 后,逐步形成越來越大的 StoreFile -> 單個 StoreFile 大小超過一定閾值后,觸發(fā) Split 操作,把當前 Region Split 成 2 個 Region,Region 會下線, 新 Split 出的 2 個孩子 Region 會被 HMaster 分配到相應的 HRegionServer 上,使得原先 1 個 Region 的壓力得以分流到 2 個 Region 上由此過程可知,HBase 只是增加數(shù)據(jù),有所得更新 和刪除操作,都是在 Compact 階段做的,所以,用戶寫操作只需要進入到內存即可立即返 回,從而保證 I/O 高性能。
寫入數(shù)據(jù)的過程補充:
工作機制:每個 HRegionServer 中都會有一個 HLog 對象,HLog 是一個實現(xiàn) Write Ahead Log 的類,每次用戶操作寫入 Memstore 的同時,也會寫一份數(shù)據(jù)到 HLog 文件,HLog 文件定期 會滾動出新,并刪除舊的文件(已持久化到 StoreFile 中的數(shù)據(jù))。當 HRegionServer 意外終止 后,HMaster 會通過 ZooKeeper 感知,HMaster 首先處理遺留的 HLog 文件,將不同 Region 的 log數(shù)據(jù)拆分,分別放到相應 Region 目錄下,然后再將失效的 Region(帶有剛剛拆分的 log) 重新分配,領取到這些 Region 的 HRegionServer 在 load Region 的過程中,會發(fā)現(xiàn)有歷史 HLog 需要處理,因此會 Replay HLog 中的數(shù)據(jù)到 MemStore 中,然后 flush 到 StoreFiles,完成數(shù)據(jù) 恢復。
任何時刻,一個 Region 只能分配給一個 RegionServer。master 記錄了當前有哪些可用的 RegionServer。以及當前哪些 Region 分配給了哪些 RegionServer,哪些 Region 還沒有分配。 當需要分配的新的 Region,并且有一個 RegionServer 上有可用空間時,Master 就給這個 RegionServer 發(fā)送一個裝載請求,把 Region 分配給這個 RegionServer。RegionServer 得到請 求后,就開始對此 Region 提供服務。
Master 使用 zookeeper 來跟蹤 RegionServer 狀態(tài)。當某個 RegionServer 啟動時,會首先在 ZooKeeper 上的 server 目錄下建立代表自己的 znode。由于 Master 訂閱了 server 目錄上的變 更消息,當 server 目錄下的文件出現(xiàn)新增或刪除操作時,Master 可以得到來自 ZooKeeper 的實時通知。因此一旦 RegionServer 上線,Master 能馬上得到消息。
當 RegionServer 下線時,它和 zookeeper 的會話斷開,ZooKeeper 而自動釋放代表這臺 server 的文件上的獨占鎖。Master 就可以確定:
1、RegionServer 和 ZooKeeper 之間的網(wǎng)絡斷開了。
2、RegionServer 掛了。
無論哪種情況,RegionServer都無法繼續(xù)為它的Region提供服務了,此時Master會刪除server 目錄下代表這臺 RegionServer 的 znode 數(shù)據(jù),并將這臺 RegionServer 的 Region 分配給其它還 活著的同志。
Master 啟動進行以下步驟:
1、從 ZooKeeper 上獲取唯一一個代表 Active Master 的鎖,用來阻止其它 Master 成為 Master。
2、掃描 ZooKeeper 上的 server 父節(jié)點,獲得當前可用的 RegionServer 列表。
3、和每個 RegionServer 通信,獲得當前已分配的 Region 和 RegionServer 的對應關系。
4、掃描.META. Region 的集合,計算得到當前還未分配的 Region,將他們放入待分配 Region 列表。
由于 Master 只維護表和 Region 的元數(shù)據(jù),而不參與表數(shù)據(jù) IO 的過程,Master 下線僅 導致所有元數(shù)據(jù)的修改被凍結(無法創(chuàng)建刪除表,無法修改表的 schema,無法進行 Region 的負載均衡,無法處理 Region 上下線,無法進行 Region 的合并,唯一例外的是 Region 的 split 可以正常進行,因為只有 RegionServer 參與),表的數(shù)據(jù)讀寫還可以正常進行。因此 Master 下線短時間內對整個 hbase 集群沒有影響。
從上線過程可以看到,Master 保存的信息全是可以冗余信息(都可以從系統(tǒng)其它地方 收集到或者計算出來)
因此,一般 HBase 集群中總是有一個 Master 在提供服務,還有一個以上的 Master 在等 待時機搶占它的位置。