HBase KeyValue格式解析
HBase的核心存儲結構是KeyValue類。這個類定義了HBase的數據模型,並貫穿了HBase的整個讀寫鏈路。同時,HBase自身的元數據管理也是使用了業務表相同的模式。所以,從底層了解KeyValue的格式和設計,會加深我們對HBase基礎架構的理解,從而更好的使用和管理HBase。
數據模型淺析
HBase的數據模型是一個松散表結構,所謂松散,包含兩個方面的含義
- 沒有schema:沒有一個地方定義了一行應該包括哪些列,這些列都是什麽類型。這個信息通常只有用戶自己知道。
- 稀疏:每行的列都可以完全不同,行與行之間的列在HBase層面沒有任何關聯
所以,我們說HBase是schema-free的,可以任意添加列。這些能力的基礎就是KeyValue的設計。
KeyValue對使用者而言是一個六元組,即(rowkey, family, qualifier, timestamp, type, value)。在1.x版本之後,添加了tags支持,變成了7元組,即(rowkey, family, qualifier, timestamp, type, value, tags)。但其設計思想是沒有變的,即key-value的方式進行存儲,從業務邏輯上看,key就是rowkey;value除了值本身,還包含了value的一些描述信息,即family、qualifier、timestamp和type。
所以,KeyValue本身在可以獨立的描述一行中的一列數據。因為帶上了列名信息,所以,不需要事先定義好一行有哪些列(schema)。也因為如此,一行中可以存在任意的列,每行的列都可以完全不同。這個能力相比傳統的RDBMS而言無疑是非常強大的,目前諸多的NoSQL系統幾乎都提供這種schema-free的能力。這個能力比較常見的應用可以是:
- 從容應對業務模型變化:設計數據庫的人都知道,業務需求變了,表設計也要跟著變,經常要添加列。而HBase這種模型,不存在“添加列”這個操作,直接寫新列就好了。
- 列名本身也可以存儲信息:因為列名本身與列值綁定在一起了,我們可以利用列名來存儲信息,比如
- 時序場景,可以用列名作為數據的時間
- 圖數據庫場景,可以用列名來描述“邊”
天下沒有免費的午餐,在獲得上述強大能力的同時,要付出的代價也是巨大的,即數據冗余,包括:
- rowkey重復存儲:一行由多個具有相同rowkey的KeyValue組成
- family,qualifier重復存儲
如果表是直接從RDBMS遷移過來的,每行都有相同的列,那無疑列名的重復會額外占用很多空間,尤其是一行中列較多的時候。這也是為什麽在表設計時,要選取盡可能短小的family名字和列名。另外,rowkey的重復也有同樣的問題。我們有一些技術可以有效的解決這些問題。
- DIFF壓縮:解決rowkey重復存儲的問題,在一行中列較多時效果非常明顯,這裏不展開。
- 列名映射:通常列名都是一些比較長的單詞或者短語,每列的列名不同,對DIFF壓縮不友好。所以,可以將易讀的列名映射為二進制的短列名(如short類型),HBase層面實際存儲的是1,2,3這樣的列名,而業務層通過一套列名映射機制在讀寫數據的時候進行列名轉換。用時間換空間。具體可以參考Phoenix的Column Name Econding(PHOENIX-1598)。
下面,我們來看一下KeyValue的數據格式。
KeyValue格式(0.94)
KeyValue本身就是一串二進制數據,即byte[],通過一些編碼規則,將二進制數據映射為六元組或七元組。下面,我們先看看094版本的KeyValue格式。
0.94版本的KeyValue的byte數組由3部分組成。
- 2個長度字段,每個字段4字節:即key的長度,value的長度
- key數據
- value數據
其中,key包括了rowkey,family,qualifier,timestamp,type,這5個部分。
- rowkey:2字節的rowkey length字段描述其長度,所以,最大的rowkey長度就是Short所能描述的最大正整數,即Short#MAX_VALUE,32KB。
- famliy:1字節的family length字段描述其長度,所以,最大的family長度是Byte#MAX_VALUE,即127字節
- qualifier:不獨立存儲其長度,通過KeyLength,rowkey長度,family長度,可以計算得到qualifier長度。
- timestamp:定長8字節,單位毫秒,時間戳
- type:1字節,描述這個KV的類型,如Put還是delete marker等
末尾是value字段,其長度由開始的ValueLength字段來定義。最大是Integer#MAX_VALUE,即4GB。但實際上,超過1MB的KV通常就會導致嚴重的性能問題了,超過64MB的KV一般來說Protobuffer很可能都無法支持其進行序列化和反序列化。所以,單純從這個意義上看,HBase本身並不適合存儲大對象(還有其他很多因素導致HBase不適合管理大塊數據,這裏不展開)。
由上面的KeyValue格式定義可見,這個格式還是很直觀、符合直覺的,易理解,代碼也容易懂,沒有玩什麽奇技淫巧。
KeyValue格式(1.x之後的版本)
與094版本相比,末尾多了一個tag區段,可以存儲任意數量的tag。tag提供了一個擴展KeyValue能力的途徑,比如行級/列級TTL,可以通過將TTL記錄在tag中來實現。用戶也可以存儲一些自定義的信息。
其他
KeyValue的格式是了解HBase底層存儲結構的第一步,還有其他一些關鍵的設計,包括:
- 各類KeyValue comparator的實現,及他們的應用
- DIFF壓縮的原理,及其對讀寫鏈路的影響
- 時間戳與多版本的原理和應用
- KeyValue在HFile和HLog中的存儲,即HLog的格式和HFile的格式
- 讀鏈路:KeyValue的查找
- meta表(root表)的設計:HBase表設計的典範
- rowkey相關的一些列問題:rowkey的設計,前綴掃描,復合主鍵實現與數據類型編碼,等等
- 。。。
後續會慢慢對這些問題和設計進行整理和分析。
HBase KeyValue格式解析