在「HBase」中, 從邏輯上來講數據大概就長這樣:
單從圖中的邏輯模型來看, HBase 和 MySQL 的區別就是:
- 將不同的列歸屬與同一個列族下
- 支持多版本數據
這看着感覺也沒有那么太大的區別呀, 它解決了 MySQL 的那些問題呢? 每一個新事物的出現, 都是為了解決原本存在的問題.
- 對寫入友好, 支持異步大批量並發寫入
- 可動態添加列
- 按列存儲數據, 不存在的列不會落盤, 節省空間. 而 MySQL 中不存在的內容也要用 null 填充
- 支持海量數據分布式存儲(BigTable 最開始就是 Google 為了解決數據存儲問題而提出來的)
- 等等
那么他是如何解決這些問題的呢? 他的數據是如何進行存儲的呢?
HBase 數據物理結構
在介紹其物理結構之前, 要先簡單提一下 LSM 樹
LSM樹
和 MySQL 所使用的B+樹一樣, 也是一種磁盤數據的索引結構. B+樹是一種對讀取友好的存儲結構, 但是當大量寫入的時候, 比如日志信息, 因為涉及到隨機寫入, 就顯得捉襟見肘了.
而「LSM樹」就是針對這種大量寫入的場景而提出的. 他的中文名字叫: 日志結構合並樹. 文件存儲的是對數據的修改操作, 數據會 append 但不會去修改原有的數據. 是順序寫入操作.
但是, 如果不管不顧的將所有的操作都順序寫入了, 大數據培訓那讀取數據的時候沒有任何根據, 需要掃描所有操作才能讀到. 「LSM 樹」的做法是, 先在內存中維護一份小的有序的數據(內存不存在隨機讀寫的問題), 當這份數據超過一定大小的時候, 將其整個放入磁盤中.
這樣, 磁盤中就存在很多個有序的文件了, 但是會有大量的小文件, 讀取數據時要依次查找, 導致讀取性能降低. 這時就需要對多個小文件進行多路歸並合成一個文件來優化讀取的性能.
至此, 基本就是「LSM 樹」的全部思想了.
- 在內存中維護一個有序的數據
- 將內存中的數據push 到磁盤中
- 將磁盤中的多個有序文件進行歸並, 合成一個較大的有序文件
HBase存儲
在「HBase」中, 數據的存儲就使用了 「LSM 樹」進行存儲. 其中每一條數據都是一條操作記錄. 那么在「HBase」實現中的部分內容如下.
「內存有序結構的實現」
通過跳表來維護內存中的有序結構, 當一個跳表裝滿之后, 將禁止新的寫入操作並將其 push 到磁盤中, 同時開一個新的數據結構來接收新到的操作請求.
「每條數據的存儲內容」
存儲了一個KV 鍵值對, 其中的 V 就是我們寫入的值, 而這個 key 由以下部分組成:
- row key
- 列族
- 列名
- 時間戳
- 操作類型: Put、Delete、DeleteColumn、DeleteFamily 等等
整個列表是 key 的順序列表. 其排序規則如下:
- row key小的排在前面
- 同 row key 比較列族
- 同列族比較列名
- 同列名比較時間戳, 時間戳大的在前面.
按照這個順序進行讀取指定 row key 的某一列數據時, 最先拿到的數據就是最新的版本, 若是 delete 操作, 說明最后執行了刪除操作, 即使后面有數據, 最新數據也是空.
「磁盤文件的結構」
由三部分組成:
- 頭信息: 存儲文件大小, 文件塊數量, 索引位置, 索引大小等信息
- 索引數據: 用戶對文件中所有數據塊進行索引, 其中每一個數據塊都包含一條索引數據, 索引內容包括
- 數據塊的最后一條數據. 用於對索引進行二分查找, 快速定位到指定的數據塊
- 數據塊在文件中的位置
- 數據塊的大小
- 布隆過濾器. 用戶在掃描時快速過濾不存在的數據塊
- 數據塊. 其中存儲了每一條 KV 數據.
按照這個結構, 上海大數據培訓用戶在進行指定row_key 讀取的時候, 每個文件的操作如下:
- 根據頭信息內容, 加載索引數據
- 通過二分查找, 找到 row_key 在哪一數據塊下
- 根據布隆過濾器過濾掉不存在的數據塊, 加速讀取
- 根據數據塊的位置和大小, 找到指定數據塊並二分查找指定數據
HBase 數據列族式存儲
先簡單回顧一下行式存儲和列式存儲.
「行式存儲」
行式存儲, 將一行數據存儲在一起, 一行數據寫完了才會寫下一行. 例如典型的 MySQL.
行式存儲在讀取一行數據的時候是比較快的, 但如果讀取的是某一列數據, 也需要將整行讀取到內存中進行過濾.
「列式存儲」
與行式存儲相對應的就是列式存儲, 既將一列數據存儲在一起, 不同列的數據分別存儲.
列式存儲對於只讀取某一列比較友好, 但相對的, 如果要讀取多列數據, 需要讀取多次並進行合並.
「列族式存儲」
而 HBase 中選用了一種折中的方案, 列族式存儲, 將列族放到一起存儲, 不同列族分別存儲.
那么也就是說, 如果一個表有多個列族, 每個列族下只有一列, 那么就等同於列式存儲
如果一個表只有一個列族, 該列族下有多個列, 那么就等同與行式存儲.
HBase 會將一張表同一列族的數據, 分配到同一個 region 上, 這個region 分配在集群中的某一個 regionServer. 所有的 region 存儲在表: hbase:meta 表中, 表結構如下:
表不同列含義如下:
- row_key 由以下字段拼接(逗號)而成
- 表名
- 起始 row_key
- 創建時間戳
- 上面三個字段的md5
- info:regioninfo 主要存儲以下數據(json)
- STARTKEY: 起始 row_key
- ENDKEY: 結束 row_key
- NAME: region 名
- ENCODED: 不清楚是什么
- info:seqnumDuringOpen 表示regionServer 在線時長
- info:server 落在哪個 regionServer 上
- info:serverstartcode regionServer 的啟動時間
- 等等