在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 的啟動時間
- 等等
總結
簡單瞭解了HBase的資料落盤格式, 也大概解釋 HBase 的很多疑惑, 比如:
- 為什麼只支援 row key 索引查詢
- 因為整個檔案是按照 row key 排序的
- 為什麼讀取效率比 MySQL 低
- 因為要依次讀取檔案進行查詢
- 為什麼支援高效率的寫入操作
- 因為全部都是順序讀寫操作
- 應該如何設定 HBase 的列族
- 將同一場景讀取的放到同一列族下, 不同場景讀取的放到不同列族下
- 等等