《HBase 不睡覺書》是一本讓人看了不會睡着的 HBase 技術書籍,寫的很是不錯,爲了加深記憶,決定把書中重要的部分整理成讀書筆記,便於後期查閱,同時但願爲初學 HBase 的同窗帶來一些幫助。算法
本文內容略長,看的時候須要一些耐心。文章首先回顧了 HBase 的數據模型和數據層級結構,對數據的每一個層級的做用和構架均進行了詳細闡述;隨後介紹了數據寫入和讀取的詳細流程;最後介紹了老版本到新版本 Region 查找的演進。數據庫
Column Family: Column Qualifier
來一塊兒表示,列是能夠隨意定義的,一個行中的列不限名字、不限數量。官方給出的答案是乾脆的,那就是「不支持」。若是想實現數據之間的關聯,就必須本身去實現了,這是挑選 NoSQL 數據庫必須付出的代價。編程
ACID 就是 Atomicity(原子性)、Consistency(一致性)、Isolation(隔離性)、Durability(持久性)的首字母縮寫,ACID 是事務正確執行的保證,HBase 部分支持 了 ACID。緩存
表命名空間主要是用來對錶分組,那麼對錶分組有什麼用?命名空間能夠填補 HBase 沒法在一個實例上分庫的缺憾,經過命名空間咱們能夠像關係型數據庫同樣將表分組,對於不一樣的組進行不一樣的環境設定,好比配額管理、安全管理等。安全
HBase 中有兩個保留表空間是預先定義好的:bash
一個 HBase 集羣由一個 Master(也能夠把兩個 Master 作成 HighAvailable)和多個 RegionServer 組成。服務器
hbase:meata
的位置存儲在 ZooKeeper 上。一個 RegionServer 包含有:數據結構
每個 Region 內都包含有多個 Store 實例,一個 Store 對應一個列族的數據,若是一個表有兩個列族,那麼在一個 Region 裏面就有兩個 Store,Store 內部有 MemStore 和 HFile 這兩個組成部分。架構
預寫日誌(Write-ahead log,WAL)就是設計來解決宕機以後的操做恢復問題的,數據到達 Region 的時候是先寫入 WAL,而後再被加載到 Memstore,就算 Region 的機器宕掉了,因爲 WAL 的數據是存儲在 HDFS 上的,因此數據並不會丟失。異步
WAL 是默認開啓的,能夠經過下面的代碼關閉 WAL。
Mutation.setDurability(Durability.SKIP_WAL);
複製代碼
Put、Append、Increment、Delete 都是 Mutation 的子類,因此他們都有 setDurability 方法,這樣可讓該數據操做快一點,可是最好不要這樣作,由於當服務器宕機時,數據就會丟失。
若是你實在想不惜經過關閉 WAL 來提升性能,能夠選擇異步寫入 WAL。
Mutation.setDurability(Durability.ASYNC_WAL);
複製代碼
這樣設定後 Region 會等到條件知足的時候才把操做寫入 WAL,這裏提到的條件主要指的是時間間隔 hbase.regionserver.optionallogflushinterval
,這個時間間隔的意思是 HBase 間隔多久會把操做從內存寫入 WAL,默認值是 1s。
若是你的系統對性能要求極高、對數據一致性要求不高,而且系統的性能瓶頸出如今 WAL 上的時候,你能夠考慮使用異步寫入 WAL,不然,使用默認的配置便可。
WAL 是一個環狀的滾動日誌結構,由於這種結構寫入效果最高,並且能夠保證空間不會持續變大。
WAL 的檢查間隔由 hbase.regionserver.logroll.period
定義,默認值爲 1h。檢查的內容是把當前 WAL 中的操做跟實際持久化到 HDFS 上的操做比較,看哪些操做已經被持久化了,被持久化的操做就會被移動到 .oldlogs
文件夾內(這個文件夾也是在 HDFS 上的)。
一個 WAL 實例包含有多個 WAL 文件,WAL 文件的最大數量經過 hbase.regionserver.maxlogs
(默認是 32)參數來定義。
其餘的觸發滾動的條件是:
hbase.regionserver.hlog.blocksize * hbase.regionserver.logroll.multiplier
;hbase.regionserver.hlog.blocksize
是標定存儲系統的塊(Block)大小的,你若是是基於 HDFS 的,那麼只須要把這個值設定成 HDFS 的塊大小便可;hbase.regionserver.logroll.multiplier
是一個百分比,默認設定成 0.95,意思是 95%,若是 WAL 文件所佔的空間大於或者等於 95% 的塊大小,則這個 WAL 文件就會被歸檔到 .oldlogs
文件夾內。WAL 文件被建立出來後會放在 /hbase/.log
下(這裏說的路徑都是基於 HDFS),一旦 WAL 文件被斷定爲要歸檔,則會被移動到 /hbase/.oldlogs
文件夾。Master 會負責按期地去清理 .oldlogs
文件夾,判斷的條件是「沒有任何引用指向這個 WAL 文件」。目前有兩種服務可能會引用 WAL 文件:
hbase.master.logcleaner.ttl
定義的超時時間(默認 10 分鐘)爲止;在 Store 中有兩個重要組成部分:
WAL 是存儲在 HDFS 上的,Memstore 是存儲在內存中的,HFile 又是存儲在 HDFS 上的;數據是先寫入 WAL,再被放入 Memstore,最後被持久化到 HFile 中。數據在進入 HFile 以前已經被存儲到 HDFS 一次了,爲何還須要被放入 Memstore?
這是由於 HDFS 上的文件只能建立、追加、刪除,可是不能修改。對於一個數據庫來講,按順序地存放數據是很是重要的,這是性能的保障,因此咱們不能按照數據到來的順序來寫入硬盤。
可使用內存先把數據整理成順序存放,而後再一塊兒寫入硬盤,這就是 Memstore 存在的意義。雖然 Memstore 是存儲在內存中的,HFile 和 WAL 是存儲在 HDFS 上的,但因爲數據在寫入 Memstore 以前,要先被寫入 WAL,因此增長 Memstore 的大小並不能加速寫入速度。Memstore 存在的意義是維持數據按照 rowkey 順序排列,而不是作一個緩存。
設計 MemStore 的緣由有如下幾點:
不過不要想固然地認爲讀取也是先讀取 Memstore 再讀取磁盤喲!讀取的時候是有專門的緩存叫 BlockCache,這個 BlockCache 若是開啓了,就是先讀 BlockCache,讀不到纔是讀 HFile+Memstore。
HFile 是數據存儲的實際載體,咱們建立的全部表、列等數據都存儲在 HFile 裏面。HFile 是由一個一個的塊組成的,在 HBase 中一個塊的大小默認爲 64KB,由列族上的 BLOCKSIZE 屬性定義。這些塊區分了不一樣的角色:
其實叫 HFile 或者 StoreFile 都沒錯,在物理存儲上咱們管 MemStore 刷寫而成的文件叫 HFile,StoreFile 就是 HFile 的抽象類而已。
Data 數據塊的第一位存儲的是塊的類型,後面存儲的是多個 KeyValue 鍵值對,也就是單元格(Cell)的實現類,Cell 是一個接口,KeyValue 是它的實現類。
一個 KeyValue 類裏面最後一個部分是存儲數據的 Value,而前面的部分都是存儲跟該單元格相關的元數據信息。若是你存儲的 value 很小,那麼這個單元格的絕大部分空間就都是 rowkey、column family、column 等的元數據,因此你們的列族和列的名字若是很長,大部分的空間就都被拿來存儲這些數據了。
不過若是採用適當的壓縮算法就能夠極大地節省存儲列族、列等信息的空間了,因此在實際的使用中,能夠經過指定壓縮算法來壓縮這些元數據。不過壓縮和解壓必然帶來性能損耗,因此使用壓縮也須要根據實際狀況來取捨。若是你的數據主要是歸檔數據,不太要求讀寫性能,那麼壓縮算法就比較適合你。
HBase 是一個能夠隨機讀寫的數據庫,而它所基於的持久化層 HDFS 倒是要麼新增,要麼整個刪除,不能修改的系統。那 HBase 怎麼實現咱們的增刪查改的?真實的狀況是這樣的:HBase 幾乎老是在作新增操做。
因爲數據庫在使用過程當中積累了不少增刪查改操做,數據的連續性和順序性必然會被破壞。爲了提高性能,HBase 每間隔一段時間都會進行一次合併(Compaction),合併的對象爲 HFile 文件。
合併分爲 minor compaction 和 major compaction,在 HBase 進行 major compaction 的時候,它會把多個 HFile 合併成 1 個 HFile,在這個過程當中,一旦檢測到有被打上墓碑標記的記錄,在合併的過程當中就忽略這條記錄,這樣在新產生的 HFile 中,就沒有這條記錄了,天然也就至關於被真正地刪除了。
HBase 數據的內部結構大致以下:
一個 KeyValue 被持久化到 HDFS 的過程的以下:
因爲有 MemStore(基於內存)和 HFile(基於HDFS)這兩個機制,你必定會立馬想到先讀取 MemStore,若是找不到,再去 HFile 中查詢。這是顯而易見的機制,惋惜 HBase 在處理讀取的時候並非這樣的。實際的讀取順序是先從 BlockCache 中找數據,找不到了再去 Memstore 和 HFile 中查詢數據。
墓碑標記和數據不在一個地方,讀取數據的時候怎麼知道這個數據要刪除呢?若是這個數據比它的墓碑標記更早被讀到,那在這個時間點真是不知道這個數據會被刪 除,只有當掃描器接着往下讀,讀到墓碑標記的時候才知道這個數據是被標記爲刪除的,不須要返回給用戶。
因此 HBase 的 Scan 操做在取到所須要的全部行鍵對應的信息以後還會繼續掃描下去,直到被掃描的數據大於給出的限定條件爲止,這樣它才能知道哪些數據應該被返回給用戶,而哪些應該被捨棄。因此你增長過濾條件也沒法減小 Scan 遍歷的行數,只有縮小 STARTROW 和 ENDROW 之間的行鍵範圍才能夠明顯地加快掃描的速度。
在 Scan 掃描的時候 store 會建立 StoreScanner 實例,StoreScanner 會把 MemStore 和 HFile 結合起來掃描,因此具體從 MemStore 仍是 HFile 中讀取數據,外部的調用者都不須要知道具體的細節。當 StoreScanner 打開的時候,會先定位到起始行鍵(STARTROW)上,而後開始往下掃描。
其中紅色塊部分都是屬於指定 row 的數據,Scan 要把全部符合條件的 StoreScanner 都掃描過一遍以後纔會返回數據給用戶。
Region 的查找,早期的設計(0.96.0)以前是被稱爲三層查詢架構:
.META.
表中的一行記錄就是一個 Region,該行記錄了該 Region 的起始行、結束行和該 Region 的鏈接信息,這樣客戶端就能夠經過這個來判斷須要的數據在哪一個 Region 上;.META.
表的表,.META.
能夠有不少張,而 -ROOT-
就是存儲了 .META.
表在什麼 Region 上的信息(.META.
表也是一張普通的表,也在 Region 上)。經過兩層的擴展最多能夠支持約 171 億個 Region。-ROOT-
表記錄在 ZooKeeper 上,路徑爲:/hbase/root-region-server
;Client 查找數據的流程從宏觀角度來看是這樣的:
/hbase/root-regionserver
節點來知道 -ROOT-
表在什麼 RegionServer 上;-ROOT-
表,看須要的數據在哪一個 .META.
表上,這個 .META.
表在什麼 RegionServer 上;.META.
表來看要查詢的行鍵在什麼 Region 範圍裏面;從 0.96 版本以後這個三層查詢架構被改爲了二層查詢架構,-ROOT-
表被去掉了,同時 zk 中的 /hbase/root-region-server
也被去掉了,直接把 .META.
表所在的 RegionServer 信息存儲到了 zk 中的 /hbase/meta-region-server
。再後來引入了 namespace,.META.
表被修改爲了 hbase:meta
。
新版 Region 查找流程:
/hbase/meta-region-server
節點查詢到哪臺 RegionServer 上有 hbase:meta
表。hbase:meta
表的 RegionServer,hbase:meta
表存儲了全部 Region 的行鍵範圍信息,經過這個表就能夠查詢出要存取的 rowkey 屬於哪一個 Region 的範圍裏面,以及這個 Region 又是屬於哪一個 RegionServer;hbase:meta
的步驟了。Any Code,Code Any!
掃碼關注『AnyCode』,編程路上,一塊兒前行。