傳統的ACID數據庫,可擴展性上受到了巨大的挑戰。而HBase這類系統,兼具可擴展性的同時,也提出了類SQL的接口。
HBase架構組成
HBase采用Master/Slave架構搭建集群,它隸屬於Hadoop生態系統,由一下類型節點組成:HMaster節點、HRegionServer節點、ZooKeeper集群,而在底層,它將數據存儲於HDFS中,因而涉及到HDFS的NameNode、DataNode等,總體結構如下:
HBase Client通過RPC方式和HMaster、HRegionServer通信
HMaster節點
- 協調HRegionServer
- 啟動時HRegion的分配,以及負載均衡和修復時HRegion的重新分配。
- 監控集群中所有HRegionServer的狀態(通過與zookeeper的Heartbeat和監聽ZooKeeper中的狀態,並不直接和slave相連)
- Admin職能:創建、刪除、修改Table的定義。
HRegionServer節點
- 存放和管理本地HRegion。
- 讀寫HDFS,管理Table中的數據。
- Client直接通過HRegionServer讀寫數據
一個HRegionServer可以存放1000個HRegion(出自BigTable);HBase使用RowKey將表水平切割成多個HRegion,從HMaster的角度,每個HRegion都紀錄了它的StartKey和EndKey(第一個HRegion的StartKey為空,最后一個HRegion的EndKey為空),由於RowKey是排序的,因而Client可以通過HMaster快速的定位每個RowKey在哪個HRegion中。
底層Table數據存儲於HDFS中,而HRegion所處理的數據盡量和數據所在的DataNode在一起,實現數據的本地化;數據本地化並不是總能實現,比如在HRegion移動(如因Split)時,需要等下一次Compact才能繼續回到本地化。
ZooKeeper集群是協調系統
- 存放整個HBase集群的元數據以及集群的狀態信息(avalible/alive)。
- 實現HMaster主從節點的failover,並在HRegionServer宕機的時候通知HMaster。
HBase的第一次讀寫
0.96以前的版本(參考BigTable)
HBase有兩個特殊的表,ROOT表(唯一)和META表。其中,ROOT表的位置保存在ZooKeeper中,它存儲了META表的RegionInfo信息。而META表 則存儲了用戶Table的RegionInfo信息,它可以被切分成多個HRegion。
- 從ZooKeeper讀取ROOT表位置
- HRegionServer中根據請求的Root表和RowKey讀取META表位置
- 最后從該HRegionServer中讀取META表的內容而獲取此次請求需要訪問的HRegion所在的位置
- 讀取內容。
- 所以需要3次讀取才能獲取目標HRegion的位置,然后第4次請求才能獲取真正的數據。所以,一般客戶端有ROOT表位置喝內容的緩存。
我們來看看為什么BigTable要選擇三級索引結構,BigTable論文里面提到,一般每個HRegion大小為128M,每行META數據1KB。所以,三級索引可以索引2^34個HRegion,可以索引很大很大的數據了。但是如果只有二級索引,我們就只能索引16TB,在大數據情況下這是完全不夠。
0.96后的版本
顯然只構建兩級索引可以大大加快查詢速度,為了兩級索引的情況下也能支持大數據量。我們可以加大每個HRegion的大小,如果每個HRegion大小為2GB,兩級索引就可以支持4PB。同樣,客戶端會緩存查詢后的HRegion位置信息。
hbase:meta表
meta表存儲了所有用戶HRegion的位置信息,它的RowKey是:tableName,regionStartKey,regionId,replicaId等,它只有info列族,這個列族包含三個列,他們分別是:
- regioninfo列是RegionInfo的proto格式:regionId,tableName,startKey,endKey,offline,split,replicaId;
- server格式:HRegionServer對應的server:port;
- serverstartcode格式是HRegionServer的啟動時間戳。
meta表(root表)結構
HRegionServer詳解
HRegionServer一般和DataNode在同一台機器上運行,實現數據的本地性。HRegionServer包含多個HRegion,由WAL(HLog)、BlockCache、MemStore、HFile組成。
WAL(Write Ahead Log, HLog)
所有寫操作都會先保證將數據寫入這個Log文件(每個HRegionServer只有一個)后,才會真正更新MemStore,最后寫入HFile中。這樣即使HRegionServer宕機,我們依然可以從HLog中恢復數據。
由於HDFS只允許同一時刻對一個文件只能一個客戶端寫入,所以對HLog只能單線程寫入。這樣很明顯會影響性能,所以再HBase1.0以后的版本,多個WAL並行寫(MultiWAL),該實現采用HDFS的多個管道寫,以單個HRegion為單位。
BlockCache
讀緩存,Hbase中有兩種(BlockCache為HRegionServer內存大小的20%)
- on-heap LruBlockCache:LruBlockCache受到java gc的影響,不穩定
- BucketCache(通常是off-heap):一般采用這種方式,自己管理內存,更加穩定
HRegion
HRegion是一個表的一部分,表的橫切的一部分
HStore
HRegion由多個Store(HStore)構成,每個HStore對應了一個Table在這個HRegion中的一個Column Family,即每個Column Family就是一個集中的存儲單元,因而最好將具有相近IO特性的Column存儲在一個Column Family,以實現高效讀取(數據局部性原理,可以提高緩存的命中率)。
HStore是HBase中存儲的核心,它實現了讀寫HDFS功能,一個HStore由一個MemStore 和0個或多個HFile組成。
- MemStore:所有數據的寫在完成WAL日志寫后,會 寫入MemStore中,由MemStore根據一定的算法將數據Flush到地層HDFS文件中(HFile),通常每個HRegion中的每個 Column Family有一個自己的MemStore。
- HFile:在HFile中的數據是按RowKey、Column Family、Column排序,對相同的Cell(即這三個值都一樣),則按timestamp倒序排列
MemStore
MemStore是一個In Memory Sorted Buffer,在每個HStore中都有一個MemStore,即它是一個HRegion的一個Column Family對應一個實例。它的排列順序以RowKey、Column Family、Column的順序以及Timestamp的倒序,如下所示:
每一次Put/Delete請求都是先寫入到MemStore中,當MemStore滿后會Flush成一個新的StoreFile(底層實現是HFile),即一個HStore(Column Family)可以有0個或多個StoreFile(HFile)。有以下三種情況可以觸發MemStore的Flush動作,需要注意的是MemStore的最小Flush單元是HRegion而不是單個MemStore。據說這是Column Family有個數限制的其中一個原因,估計是因為太多的Column Family一起Flush會引起性能問題
- MemStore超過128M,此時當前的HRegion中所有的MemStore會Flush到HDFS中。
- HRegionServer上所有MemStore的大小超過了機器上默認40%的內存使用量。此時當前HRegionServer中所有HRegion中的MemStore都會Flush到HDFS中,Flush順序是MemStore大小的倒序,直到低於某個閾值,默認38%
- WAL過大,當前HRegionServer中所有HRegion中的MemStore都會Flush到HDFS中,Flush使用時間順序,最早的MemStore先Flush直到WAL的數量少於某個閾值。
在MemStore Flush過程中,還會在尾部追加一些meta數據,其中就包括Flush時最大的WAL sequence值,以告訴HBase這個StoreFile寫入的最新數據的序列,那么在Recover時就直到從哪里開始。
HFile格式
HFile是MemStore在HDFS上的實體,所以寫一個HFile是順序寫,速度很快。HFile一共經歷了三個版本
v1
數據存放再Data塊中,Data塊的大小可以用戶指定,大的Data塊適合scan,小的Data塊適合隨機查找。
HFile里面的每個KeyValue對就是一個簡單的byte數組。但是這個byte數組里面包含了很多項,並且有固定的結構。
Block Index:使用記錄每個Data Block最大值的方案,需要將索引一次性讀入內存
v2
解決了V1版本內存占用,特別是Bloom File和Block Index過大。它的解決方案是把Bloom File和Block Index打散放入Data,每次查詢不用加載全部信息。對HFileV2格式具體分析,它是一個多層的類B+樹索引,采用這種設計,可以實現查找不需要讀取整個文件
Data Block中的Cell都是升序排列,每個block都有它自己的Leaf-Index,每個Block的最后一個Key被放入Intermediate-Index中,Root-Index指向Intermediate-Index。Bloom過濾器用於快速定位沒有在DataBlock中的數據
v3
v3和v2沒有太大變化,只是加了一個tag字段
理一理HRegionServer中的結構
- HRegionServer
- WAL(Hlog,一個,HDFS中)
- BlockCache
- HRegion(最多1000個,表的橫切,rowkey不會重疊)
- HStore(列族,HRegion的列切, 多個)
- MemStore(寫完Hlog后,剛生成的數據)
- HFile(多個,具體的數據,排序,HDFS中)
- HStore(列族,HRegion的列切, 多個)
HRegionServer寫流程
- 客戶端發起一個Put請求時,首先它從hbase:meta表中查出該Put數據最終需要去的HRegionServer。然后客戶端將Put請求發送給相應的HRegionServer,在HRegionServer中它首先會將該Put操作寫入WAL日志文件中
- 完WAL日志文件后,HRegionServer根據Put中的TableName和RowKey找到對應的HRegion,並根據Column Family找到對應的HStore,並將Put寫入到該HStore的MemStore中。寫入成功,返回給客戶端。
- 當MemStore積累一定量的數據后,flush成HFile到HDFS上
HBase讀流程
HBase寫時,相同Cell(RowKey/ColumnFamily/Column相同)並不保證在一起,甚至刪除一個Cell也只是寫入一個新的Cell,它含有Delete標記,而不一定將一個Cell真正刪除了,因而這就引起了一個問題,如何實現讀的問題?
相同的cell可能存在3個不同的位置,Block Cache,MemStore,HFile中。
- 從Block Cache中讀取
- 從MemStore中讀取
- 從多個HFile中讀取,用Bloom Filter篩掉明顯不存在所需數據的HFile,Index用於快速定位HFile中的數據塊
Compaction
HFile過多,在數據讀取的時候,會產生性能問題。所以一段時間后,HFile會進行合並。HBase中Compaction分為兩種:Minor Compaction和Major Compaction。
- Minor Compaction:是指選取一些小的、相鄰的HFile將他們合並成一個更大的StoreFile,在這個過程中不會處理已經Deleted或Expired的Cell。(BigTable中將memtable的數據flush的一個HFile/SSTable稱為一次Minor Compaction)
- Major Compaction:是指將所有的HFile合並成一個HFile,可以手動觸發或者自動觸發,但是會引起性能問題,一般安排在周末。
HRegion Split
最初,一個Table只有一個HRegion,隨着數據寫入增加,如果一個HRegion到達一定的大小,就需要Split成兩個HRegion,這個大小由hbase.hregion.max.filesize指定,默認為10GB。
當split時(split時停止服務),兩個新的HRegion會在同一個HRegionServer中創建,它們各自包含父HRegion一半的數據,當Split完成后,父HRegion會下線,而新的兩個子HRegion會向HMaster注冊上線,處於負載均衡的考慮,這兩個新的HRegion可能會被HMaster分配到其他的HRegionServer中。
分裂流程
- RegionServer在本地決定分割HRegion,並准備分割。第一步,匯報給zookeeper。
- master獲取zookeeper中的狀態。
- RegionServer在HDFS的父目錄區域目錄下創建一個名為“.splits”的子目錄。
- RegionServer關閉父HRegion,強制刷新緩存,並將該區域標記為本地數據結構中的脫機狀態。此時,來到父區域的客戶端請求將拋出NotServingRegionException異常。客戶端將重試一些備用值。
- RegionServer在.splits目錄下創建Region目錄,為子區域A和B創建必要的數據結構。然后,它分割存儲文件,因為它在父區域中為每個存儲文件創建兩個引用文件。那些引用文件將指向父Region文件。
- RegionServer在HDFS中創建實際的區域目錄,並移動每個子Region的引用文件。
- RegionServer向META表發送請求。將父HRegion設置為.META中的脫機狀態,並添加關於子HRegion的信息。在這時,在META中不會為女兒分配單獨的條目。客戶端會看到父區域是分割的,如果他們掃描.META,但不知道子HRegion,直到他們出現在.META。
- RegionServer並行open子HRegion接受寫入。
- RegionServer將女兒A和B添加到.META。以及它所在地區的信息。此后,客戶可以發現新的地區,並向新的地區發出請求,之前的緩存失效。
- HRegion Server向zookeeper匯報split結束的消息,master進行負載均衡。
- 拆分后,meta表和HDFS仍將包含對父HRegion的引用。當子HRegion進行Compaction時,這些引用信息會被刪除。Master也會定期檢查子HRegion,如果沒有父HRegion中的信息,父HRegion將被刪除。
HRegion分裂后負載均衡
出於負載均衡的考慮,HMaster可能會將其中的一個甚至兩個重新分配的其他的HRegionServer中。可能會產生HFile在其他節點上,直到下一次Major Compaction將數據從遠端的節點移動到本地節點。
既然有拆分,但是HRegion也可以合並。HRegion調用closeAndMerge把兩個HRegion合並(需要兩個HRegion停止服務)
HBase中的負載均衡
負載均衡器會平衡系統中每個HRegionServer中HRegion個數。
負載均衡對系統性能影響很大,實際一般關閉,每周開啟一次。
容錯
HRegionServer Recovery
- zookeeper感知,通知HMaster
- 重新分配HRegion到其他節點
- 為每個HRegion拆分WAL,將拆分出的WAL文件寫入對應的目的HRegionServer的WAL目錄中,並並寫入對應的DataNode中
- 回放WAL,重建MemStore
HMaster Recovery
依靠zookeeper進行主備切換
HBase一致性
HBase是強一致性,它表現在:
- HRegion split時相關HRegion不可用
- HRegion合並時相關HRegion不可用
- HRegionServer Recovery時相關HRegion不可用