《HBase 不睡覺》第五章 - HBase 內部探險

《HBase 不睡覺書》是一本讓人看了不會睡着的 HBase 技術書籍,寫的很是不錯,爲了加深記憶,決定把書中重要的部分整理成讀書筆記,便於後期查閱,同時但願爲初學 HBase 的同窗帶來一些幫助。算法

目錄

本文內容略長,看的時候須要一些耐心。文章首先回顧了 HBase 的數據模型和數據層級結構,對數據的每一個層級的做用和構架均進行了詳細闡述;隨後介紹了數據寫入和讀取的詳細流程;最後介紹了老版本到新版本 Region 查找的演進。數據庫

1、數據模型

一、重要概念回顧

  • Namespace(表命名空間):將多個表分到一個組進行統一管理。
  • Table(表):一個表由一個或者多個列族組成;數據屬性好比:超時時間(TTL),壓縮算法(COMPRESSION)等,都在列族的定義中定義;定義完列族後表是空的,只有添加了行,表纔有數據。
  • Row(行):一個行包含了多個列,這些列經過列族來分類;行中的數據所屬列族只能從該表所定義的列族中選取;因爲 HBase 是一個列式數據庫,因此一個行中的數據能夠分佈在不一樣的服務器上。
  • Column Family(列族):列族是多個列的集合,HBase 會盡可能把同一個列族的列放到同一個服務器上,這樣能夠提升存取性能,而且能夠批量管理有關聯的一堆列;全部的數據屬性都是定義在列族上;在 HBase 中,建表定義的不是列,而是列族。
  • Column Qualifier(列):多個列組成一個行,列族和列常常用 Column Family: Column Qualifier 來一塊兒表示,列是能夠隨意定義的,一個行中的列不限名字、不限數量。
  • Cell(單元格):一個列中能夠存儲多個版本的數據,而每一個版本就稱爲一個單元格(Cell),因此在 HBase 中的單元格跟傳統關係型數據庫的單元格概念不同;HBase 中的數據細粒度比傳統數據結構更細一級,同一個位置的數據還細分紅多個版本。
  • Timestamp(時間戳/版本號):既能夠把它稱爲是時間戳,也能夠稱爲是版本號,由於它是用來標定同一個列中多個單元格的版本號的。不指定版本號的時候,系統會自動採用當前的時間戳來做爲版本號;當手動定義了一個數字來看成版本號的時候,這個 Timestamp 就真的是隻有版本號的意義了。

二、幾個小問題

HBase是否支持表關聯?

官方給出的答案是乾脆的,那就是「不支持」。若是想實現數據之間的關聯,就必須本身去實現了,這是挑選 NoSQL 數據庫必須付出的代價。編程

HBase 是否支持 ACID?

ACID 就是 Atomicity(原子性)、Consistency(一致性)、Isolation(隔離性)、Durability(持久性)的首字母縮寫,ACID 是事務正確執行的保證,HBase 部分支持 了 ACID。緩存

表命名空間有什麼用?

表命名空間主要是用來對錶分組,那麼對錶分組有什麼用?命名空間能夠填補 HBase 沒法在一個實例上分庫的缺憾,經過命名空間咱們能夠像關係型數據庫同樣將表分組,對於不一樣的組進行不一樣的環境設定,好比配額管理、安全管理等。安全

HBase 中有兩個保留表空間是預先定義好的:bash

  • hbase:系統表空間,用於組織 HBase 內部表;
  • default:那些沒有定義表空間的表都被自動分配到這個表空間下。

2、HBase 的存儲數據方式

一、架構回顧

一個 HBase 集羣由一個 Master(也能夠把兩個 Master 作成 HighAvailable)和多個 RegionServer 組成。服務器

  • Master:負責啓動的時候分配 Region 到具體的 RegionServer,執行各類管理操做,好比 Region 的分割和合並。HBase 中的 Master 的角色功能比其餘類型集羣弱不少,HBase 的 Master 很特別,由於數據的讀取和寫入都跟它沒什麼關係,它掛了業務系統照樣運行。固然 Master 也不能宕機過久,有不少必要的操做,好比建立表、修改列族配置,以及更重要的分割和合並都須要它的操做。
  • RegionServer:RegionServer 上有一個或者多個 Region,咱們讀寫的數據就存儲在 Region 上。若是你的 HBase 是基於 HDFS 的(單機 HBase 可基於本地磁盤),那麼 Region 全部數據存取操做都是調用了 HDFS 的客戶端接口來實現的。
  • Region:表的一部分數據,HBase 是一個會自動分片的數據庫,一個 Region 就至關於關係型數據庫中分區表的一個分區,或者 MongoDB 的一個分片。
  • HDFS:HBase 並不直接跟服務器的硬盤交互,而是跟 HDFS 交互,因此 HDFS 是真正承載數據的載體。
  • ZooKeeper:ZooKeeper 在 HBase 中的比 Master 更重要,把 Master 關掉業務系統照樣跑,能讀能寫;可是把 ZooKeeper 關掉,就不能讀取數據了,由於讀取數據所須要的元數據表 hbase:meata 的位置存儲在 ZooKeeper 上。

HBase 的宏觀架構

二、RegionServer 內部架構

一個 RegionServer 包含有:數據結構

  • 一個 WAL:預寫日誌,WAL 是 Write-Ahead Log 的縮寫,就是:預先寫入。當操做到達 Region 的時候,HBase 先把操做寫到 WAL 裏面去,HBase 會把數據放到基於內存實現的 Memstore 裏,等數據達到必定的數量時才刷寫(flush)到最終存儲的 HFile 內,而若是在這個過程當中服務器宕機或者斷電了,那麼數據就丟失了。WAL 是一個保險機制,數據在寫到 Memstore 以前,先被寫到 WAL 了,這樣當故障恢復的時候依舊能夠從 WAL 中恢復數據。
  • 多個 Region:Region 至關於一個數據分片,每個 Region 都有起始 rowkey 和結束 rowkey,表明了它所存儲的 row 範圍。

RegionServer 內部架構

三、Region 內部架構

每個 Region 內都包含有多個 Store 實例,一個 Store 對應一個列族的數據,若是一個表有兩個列族,那麼在一個 Region 裏面就有兩個 Store,Store 內部有 MemStore 和 HFile 這兩個組成部分。架構

Region 內部架構

四、預寫日誌(WAL)

預寫日誌(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 是一個環狀的滾動日誌結構,由於這種結構寫入效果最高,並且能夠保證空間不會持續變大。

WAL 的檢查間隔由 hbase.regionserver.logroll.period 定義,默認值爲 1h。檢查的內容是把當前 WAL 中的操做跟實際持久化到 HDFS 上的操做比較,看哪些操做已經被持久化了,被持久化的操做就會被移動到 .oldlogs 文件夾內(這個文件夾也是在 HDFS 上的)。

一個 WAL 實例包含有多個 WAL 文件,WAL 文件的最大數量經過 hbase.regionserver.maxlogs(默認是 32)參數來定義。

其餘的觸發滾動的條件是:

  • 當 WAL 文件所在的塊(Block)快要滿了;
  • 當WAL所佔的空間大於或者等於某個閥值,該閥值的計算公式是: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 文件:

  • TTL 進程:該進程會保證 WAL 文件一直存活直到達到 hbase.master.logcleaner.ttl 定義的超時時間(默認 10 分鐘)爲止;
  • 備份(replication)機制:若是開啓了 HBase 的備份機制,那麼 HBase 要保證備份集羣已經徹底不須要這個 WAL 文件了,纔會刪除這個 WAL 文件。這裏提到的 replication 不是文件的備份數,而是 0.90 版本加入的特性,這個特性用於把一個集羣的數據實時備份到另一個集羣。

六、Store 內部結構

在 Store 中有兩個重要組成部分:

  • MemStore:每一個 Store 中有一個 MemStore 實例,數據寫入 WAL 以後就會被放入 MemStore。MemStore 是內存的存儲對象,只有當 MemStore 滿了的時候纔會將數據刷寫(flush)到 HFile 中;
  • HFile:在 Store 中有多個 HFile,當 MemStore 滿了以後 HBase 就會在 HDFS 上生成一個新的 HFile,而後把 MemStore 中的內容寫到這個 HFile 中。HFile 直接跟 HDFS 打交道,它是數據的存儲實體。

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 的緣由有如下幾點:

  • 因爲 HDFS 上的文件不可修改,爲了讓數據順序存儲從而提升讀取效率,HBase 使用了 LSM 樹結構來存儲數據,數據會先在 Memstore 中整理成 LSM 樹,最後再刷寫到 HFile 上。
  • 優化數據的存儲,好比一個數據添加後就立刻刪除了,這樣在刷寫的時候就能夠直接不把這個數據寫到 HDFS 上。

不過不要想固然地認爲讀取也是先讀取 Memstore 再讀取磁盤喲!讀取的時候是有專門的緩存叫 BlockCache,這個 BlockCache 若是開啓了,就是先讀 BlockCache,讀不到纔是讀 HFile+Memstore。

八、HFile(StoreFile)

HFile 是數據存儲的實際載體,咱們建立的全部表、列等數據都存儲在 HFile 裏面。HFile 是由一個一個的塊組成的,在 HBase 中一個塊的大小默認爲 64KB,由列族上的 BLOCKSIZE 屬性定義。這些塊區分了不一樣的角色:

  • Data:數據塊。每一個 HFile 有多個 Data 塊,咱們存儲在 HBase 表中的數據就在這裏,Data 塊實際上是可選的,可是幾乎很難看到不包含 Data 塊的 HFile;
  • Meta:元數據塊。Meta 塊是可選的,Meta 塊只有在文件關閉的時候纔會寫入。Meta 塊存儲了該 HFile 文件的元數據信息,在 v2 以前布隆過濾器(Bloom Filter)的信息直接放在 Meta 裏面存儲,v2 以後分離出來單獨存儲;
  • FileInfo:文件信息,其實也是一種數據存儲塊。FileInfo 是 HFile 的必要組成部分,是必選的,它只有在文件關閉的時候寫入,存儲的是這個文件的信息,好比最後一個 Key(LastKey),平均的 Key 長度(AvgKeyLen)等;
  • DataIndex:存儲 Data 塊索引信息的塊文件。索引的信息其實也就是 Data 塊的偏移值(offset),DataIndex 也是可選的,有 Data 塊纔有 DataIndex;
  • MetaIndex:存儲 Meta 塊索引信息的塊文件。MetaIndex 塊也是可選的,有 Meta 塊纔有 MetaIndex;
  • Trailer必選的,它存儲了 FileInfo、DataIndex、MetaIndex 塊的偏移值。

HFile 組成結構

其實叫 HFile 或者 StoreFile 都沒錯,在物理存儲上咱們管 MemStore 刷寫而成的文件叫 HFile,StoreFile 就是 HFile 的抽象類而已。

九、Data 數據塊

Data 數據塊的第一位存儲的是塊的類型,後面存儲的是多個 KeyValue 鍵值對,也就是單元格(Cell)的實現類,Cell 是一個接口,KeyValue 是它的實現類。

Data 數據塊結構

十、KeyValue 類

一個 KeyValue 類裏面最後一個部分是存儲數據的 Value,而前面的部分都是存儲跟該單元格相關的元數據信息。若是你存儲的 value 很小,那麼這個單元格的絕大部分空間就都是 rowkey、column family、column 等的元數據,因此你們的列族和列的名字若是很長,大部分的空間就都被拿來存儲這些數據了。

不過若是採用適當的壓縮算法就能夠極大地節省存儲列族、列等信息的空間了,因此在實際的使用中,能夠經過指定壓縮算法來壓縮這些元數據。不過壓縮和解壓必然帶來性能損耗,因此使用壓縮也須要根據實際狀況來取捨。若是你的數據主要是歸檔數據,不太要求讀寫性能,那麼壓縮算法就比較適合你。

KeyValue 結構

3、增刪查改的真正面目

HBase 是一個能夠隨機讀寫的數據庫,而它所基於的持久化層 HDFS 倒是要麼新增,要麼整個刪除,不能修改的系統。那 HBase 怎麼實現咱們的增刪查改的?真實的狀況是這樣的:HBase 幾乎老是在作新增操做。

  • 當你新增一個單元格的時候,HBase 在 HDFS 上新增一條數據;
  • 當你修改一個單元格的時候,HBase 在 HDFS 又新增一條數據,只是版本號比以前那個大(或者你本身定義);
  • 當你刪除一個單元格的時候,HBase 仍是新增一條數據!只是這條數據沒有 value,類型爲 DELETE,這條數據叫墓碑標記(Tombstone)。

因爲數據庫在使用過程當中積累了不少增刪查改操做,數據的連續性和順序性必然會被破壞。爲了提高性能,HBase 每間隔一段時間都會進行一次合併(Compaction),合併的對象爲 HFile 文件。

合併分爲 minor compaction 和 major compaction,在 HBase 進行 major compaction 的時候,它會把多個 HFile 合併成 1 個 HFile,在這個過程當中,一旦檢測到有被打上墓碑標記的記錄,在合併的過程當中就忽略這條記錄,這樣在新產生的 HFile 中,就沒有這條記錄了,天然也就至關於被真正地刪除了。

4、HBase 數據結構總結

HBase 數據的內部結構大致以下:

  • 一個 RegionServer 包含多個 Region,劃分規則是:一個表的一段鍵值在一個 RegionServer 上會產生一個 Region。不過當某一行的數據量太大了(要很是大),HBase 也會把這個 Region 根據列族切分到不一樣的機器上去;
  • 一個 Region 包含多個 Store,劃分規則是:一個列族分爲一個 Store,若是一個表只有一個列族,那麼這個表在這個機器上的每個 Region 裏面都只有一個 Store;
  • 一個 Store 裏面只有一個 Memstore;
  • 一個 Store 裏面有多個 HFile,每次 Memstore 的刷寫(flush)就產生一個新的 HFile 出來。

數據單元層次圖

5、KeyValue 的寫入和讀出

一、寫入

一個 KeyValue 被持久化到 HDFS 的過程的以下:

KeyValue 寫入過程

  • WAL:數據被髮出以後第一時間被寫入 WAL,因爲 WAL 是基於 HDFS 來實現的,因此也能夠說如今單元格就已經被持久化了,可是 WAL 只是一個暫存的日誌,它是不區分 Store 的,這些數據是不能被直接讀取和使用;
  • Memstore:數據隨後會當即被放入 Memstore 中進行整理,Memstore 會負責按照 LSM 樹的結構來存放數據,這個過程就像咱們在打牌的時候,抓牌以後在手上對牌進行整理的過程;
  • HFile:最後,當 Memstore 太大了達到尺寸上的閥值,或者達到了刷寫時間間隔閥值的時候,HBaes 會把這個 Memstore 的內容刷寫到 HDFS 系統上,稱爲一個存儲在硬盤上的 HFile 文件。至此,咱們能夠稱爲數據真正地被持久化到硬盤上,就算宕機,斷電,數據也不會丟失了。

二、讀出

因爲有 MemStore(基於內存)和 HFile(基於HDFS)這兩個機制,你必定會立馬想到先讀取 MemStore,若是找不到,再去 HFile 中查詢。這是顯而易見的機制,惋惜 HBase 在處理讀取的時候並非這樣的。實際的讀取順序是先從 BlockCache 中找數據,找不到了再去 Memstore 和 HFile 中查詢數據。

墓碑標記和數據不在一個地方,讀取數據的時候怎麼知道這個數據要刪除呢?若是這個數據比它的墓碑標記更早被讀到,那在這個時間點真是不知道這個數據會被刪 除,只有當掃描器接着往下讀,讀到墓碑標記的時候才知道這個數據是被標記爲刪除的,不須要返回給用戶。

因此 HBase 的 Scan 操做在取到所須要的全部行鍵對應的信息以後還會繼續掃描下去,直到被掃描的數據大於給出的限定條件爲止,這樣它才能知道哪些數據應該被返回給用戶,而哪些應該被捨棄。因此你增長過濾條件也沒法減小 Scan 遍歷的行數,只有縮小 STARTROW 和 ENDROW 之間的行鍵範圍才能夠明顯地加快掃描的速度

在 Scan 掃描的時候 store 會建立 StoreScanner 實例,StoreScanner 會把 MemStore 和 HFile 結合起來掃描,因此具體從 MemStore 仍是 HFile 中讀取數據,外部的調用者都不須要知道具體的細節。當 StoreScanner 打開的時候,會先定位到起始行鍵(STARTROW)上,而後開始往下掃描。

StoreScanne 掃描數據

其中紅色塊部分都是屬於指定 row 的數據,Scan 要把全部符合條件的 StoreScanner 都掃描過一遍以後纔會返回數據給用戶。

6、Region 的定位

Region 的查找,早期的設計(0.96.0)以前是被稱爲三層查詢架構:

三層查詢架構

  • Region:查找的數據所在的 Region;
  • .META.:是一張元數據表,它存儲了全部 Region 的簡要信息,.META. 表中的一行記錄就是一個 Region,該行記錄了該 Region 的起始行、結束行和該 Region 的鏈接信息,這樣客戶端就能夠經過這個來判斷須要的數據在哪一個 Region 上;
  • -ROOT-:是一張存儲 .META. 表的表,.META. 能夠有不少張,而 -ROOT- 就是存儲了 .META. 表在什麼 Region 上的信息(.META. 表也是一張普通的表,也在 Region 上)。經過兩層的擴展最多能夠支持約 171 億個 Region。

-ROOT- 表記錄在 ZooKeeper 上,路徑爲:/hbase/root-region-server;Client 查找數據的流程從宏觀角度來看是這樣的:

  • 用戶經過查找 zk(ZooKeeper)的 /hbase/root-regionserver 節點來知道 -ROOT- 表在什麼 RegionServer 上;
  • 訪問 -ROOT- 表,看須要的數據在哪一個 .META. 表上,這個 .META. 表在什麼 RegionServer 上;
  • 訪問 .META. 表來看要查詢的行鍵在什麼 Region 範圍裏面;
  • 鏈接具體的數據所在的 RegionServer,這回就真的開始用 Scan 來遍歷 row 了。

早期版本 Region 查找過程

從 0.96 版本以後這個三層查詢架構被改爲了二層查詢架構,-ROOT- 表被去掉了,同時 zk 中的 /hbase/root-region-server 也被去掉了,直接把 .META. 表所在的 RegionServer 信息存儲到了 zk 中的 /hbase/meta-region-server。再後來引入了 namespace,.META. 表被修改爲了 hbase:meta

新版 Region 查找流程:

  • 客戶端先經過 ZooKeeper 的 /hbase/meta-region-server 節點查詢到哪臺 RegionServer 上有 hbase:meta 表。
  • 客戶端鏈接含有 hbase:meta 表的 RegionServer,hbase:meta 表存儲了全部 Region 的行鍵範圍信息,經過這個表就能夠查詢出要存取的 rowkey 屬於哪一個 Region 的範圍裏面,以及這個 Region 又是屬於哪一個 RegionServer;
  • 獲取這些信息後,客戶端就能夠直連其中一臺擁有要存取的 rowkey 的 RegionServer,並直接對其操做;
  • 客戶端會把 meta 信息緩存起來,下次操做就不須要進行以上加載 hbase:meta 的步驟了。

新版 Region 查找過程


Any Code,Code Any!

掃碼關注『AnyCode』,編程路上,一塊兒前行。

相關文章
相關標籤/搜索