總結《HBase原理與實踐》第六章
目錄
3.1.2 執行next函式獲取KeyValue並對其進行條件過濾
1. HBase寫入流程
1.1 寫入流程的三個階段
從整體架構看,主要是分為三個階段:
- 客戶端將使用者的寫入請求進行預處理,並根據叢集元資料定位寫入資料所在的RegionServer,將請求傳送給對應的RegionServer。
- 客戶端將使用者的寫入請求進行預處理,並根據叢集元資料定位寫入資料所在的RegionServer,將請求傳送給對應的RegionServer。
- MemStore Flush階段:當Region中MemStore容量超過一定閾值,系統會非同步執行f lush操作,將記憶體中的資料寫入檔案,形成HFile。
使用者寫入請求在完成Region MemStore的寫入之後就會返回成功。MemStore Flush是一個非同步執行的過程。
1.1.1 客戶端請求階段
1. 在提交put請求前,客戶端會根據寫入的表和rowkey在快取的元資料中查詢,如果找到則直接傳送請求到該region所在regionServer上,如果傳送的請求失敗,說明元資料資訊發生變化,需要重新到zookeeper中/hbase-root/meta-region-server節點查詢HBase元資料表所在的RegionServer。然後到該RegionServer在元資料表中查詢rowkey所在的RegionServer以及Region資訊。客戶端接收到返回結果之後會將結果快取到本地,以備後續使用。
怎麼查詢region資訊,之前說過通過反向查詢rowkey,才能才能找到rowkey所在的真正的region。
為什麼? 因為hbase:meta表設計時,只有Region的StartRow。如果正向查詢rowkey所在的region,會找到下一個region上去。而且即使找到下一個region,也無法快速定位該目標region。
2. 客戶端根據rowkey相關元資料資訊將寫入請求傳送給目標RegionServer,Region Server接收到請求之後會解析出具體的Region資訊,查到對應的Region物件,並將資料寫入目標Region的MemStore中。
3. 客戶端將請求序列化,通過rpc將請求傳送到RegionServer上。
1.1.2Region寫入階段
1. 加行鎖,保證行級別的原子性,要麼成功,要麼失敗。
2. 更新key-value的實踐戳為當前系統時間。
3. 欲寫入機制WAL,先將資料寫入日誌。
4. 將資料寫入MemStore中。
5. 釋放鎖。
6. HLog真正同步到HDFS,如果HLog同步失敗,將MemStore中的資料擦除。
7. 結束寫事務。
1.1.3MemStore Flush階段
隨著資料的不斷寫入,會將Memstore中的資料flush寫入檔案形成HFile。
1.1.3.1 Flush觸發條件
- MemStore級別,當region中任意一個MemStore達到128M(預設),會觸發flush。
- Region級別,當Region所有的MemStore大小達到上限,會觸發flush。
- RegionServer級別,當RegionServer中所有的MemStore大小總和超過低水位閾值,強制flush,先刷最大的Region,再刷次大的,如果此時寫入吞吐量依然很高,導致總MemStore大小超過高水位閾值hbase.regionserver.global.memstore.size,RegionServer會阻塞更新並強制執行flush,直至總MemStore大小下降到低水位閾值。
- 一個RegionServer中HLog數量達到上限。
- HBase定期重新整理MemStore,預設1小時,最近的1小時沒有新資料寫入,觸發flush。
- 手動flush, 使用者可以對錶 或 region 進行手動刷寫。
1.1.3.2 flush執行流程
為了減少flush過程對讀寫的影響,HBase採用了類似於兩階段提交的方式,將整個flush過程分為三個階段。
- prepare階段:將concurrentSkipListMap做一個快照,然後在新建一個concurrentSkipListMapj接受新寫入MemStore的資料。
- flush階段:遍歷所有MemStore,將prepare階段生成的snapshot持久化為臨時檔案,臨時檔案會統一放到目錄.tmp下。這個過程因為涉及磁碟IO操作,相對比較耗時。
- commit階段:遍歷所有的MemStore,將f lush階段生成的臨時檔案移到指定的ColumnFamily目錄下。
1.1.3.3 生成HFile
MemStore中KV在flush成HFile時首先構建Scanned Block部分,即KV寫進來之後先構建Data Block並依次寫入檔案,在形成Data Block的過程中也會依次構建形成Leaf index Block、Bloom Block並依次寫入檔案。一旦MemStore中所有KV都寫入完成,Scanned Block部分就構建完成。
Non-scanned Block、Load-on-open以及Trailer這三部分是在所有KV資料完成寫入後再追加寫入的。
從上至下寫,沒有發生隨機寫入。
1.1.3.4MemStore Flush對業務的影響
大部分MemStore Flush操作都不會對業務讀寫產生太大影響。系統定期重新整理MemStore、手動執行f lush操作、觸發MemStore級別限制、觸發HLog數量限制以及觸發Region級別限制等,這幾種場景只會阻塞對應Region上的寫請求,且阻塞時間較短。
然而,一旦觸發RegionServer級別限制導致flush,就會對使用者請求產生較大的影響。系統會阻塞所有落在該RegionServer上的寫入操作,直至MemStore中資料量降低到配置閾值內。
2. BulkLoad
BulkLoad首先使用MapReduce將待寫入叢集資料轉換為HFile檔案,再直接將這些HFile檔案載入到線上叢集中。
2.1 核心功能
- HFile生成階段。這個階段會執行一個MapReduce任務,MapReduce的mapper需要自己實現,Reduce由Hbase實現。
- HFile匯入階段。HFile準備就緒之後,就可以使用工具completebulkload將HFile載入到線上HBase叢集。
如果使用者生成HFile所在的HDFS叢集和HBase所在HDFS叢集是同一個,則MapReduce生成HFile時,能夠保證HFile與目標Region落在同一個機器上,這樣就保證了locality。但是有一些使用者會先通過MapReduce任務在HDFS叢集A上生成HFile,再通過distcp工具將資料拷貝到HDFS叢集B上去。這樣Bulkload到HBase叢集的資料是沒法保證locality的,因此需要跑完Bulkload之後再手動執行major compact,來提升locality。
3. HBase讀取流程
HBase查詢資料遇到的困難:
- 一次查詢可能涉及到多個region,多個BlockCache,多個Memstore,多個HFile。
- Hbase沒有實現真正的更新和刪除,而是使用多版本和新增‘delete’標籤。
3.1 Client-Server讀取互動邏輯
從技術實現的角度來看,get請求也是一種scan請求(最簡單的scan請求,scan的條數為1)。從這個角度講,所有讀取操作都可以認為是一次scan操作。
scan以region為單位
RegionServer接收到請求做了兩件事:
3.1.1構建Scanner Iterator體系
Scanner的核心體系包括三層Scanner:RegionScanner,StoreScanner,MemStoreScanner和StoreFileScanner。
一個region有多個storeScanner,一個列簇對應一個storeScanner ,一個StoreScanner由MemStoreScanner和StoreFileScanner構成。一個HFile對應一個StoreFileScanner
注意點:RegionScanner以及StoreScanner 承擔排程任務,負責查詢的是MemStoreScanner和StoreFileScanner
- 過濾大部分不符合條件的HFile
- 每個scanner seek到startRow
- keyValueScanner 合併構建最小堆。按照key從小到大排序。(歸併排序)
3.1.2 執行next函式獲取KeyValue並對其進行條件過濾
經過Scanner體系的構建,KeyValue此時已經可以由小到大依次經過KeyValueScanner獲得,但這些KeyValue是否滿足使用者設定的TimeRange條件、版本號條件以及Filter條件還需要進一步的檢查。
總結:第一步只是將Region所有的StoreFile 把不符合的HFile過濾掉,然後將多個檔案按照key從小到大歸併排序。第二部才會將真正符合要求的key過濾出來。如(time,版本,delete標識等)。
過濾HFile的幾種方法:
- 根據keyRanger過濾:因為StoreFile中所有KeyValue資料都是有序排列的,所以如果待檢索row範圍[ startrow,stoprow ]與檔案起始key範圍[ firstkey,lastkey]沒有交集,比如stoprow < f irstkey或者startrow > lastkey,就可以過濾掉該StoreFile。
- 根據TimeRange過濾:HFile的元資料有時間屬性,如果時間不符合,那麼也會過濾掉HFile。
- 根據布隆過濾器過濾:系統根據待檢索的rowkey獲取對應的Bloom Block並載入到記憶體(通常情況下,熱點Bloom Block會常駐記憶體的),再用hash函式對待檢索rowkey進行hash,根據hash後的結果在布隆過濾器資料中進行定址,即可確定待檢索rowkey是否一定不存在於該HFile。
3.1.3從HFile中讀取待查詢Key
1. 根據HFile索引樹定位目標Block,會將HFile的load-on-open和Trailer載入到記憶體,Load-on-open部分有個非常重要的Block——Root Index Block,即索引樹的根節點。HFile索引樹索引在資料量不大的時候只有最上面一層,隨著資料量增大開始分裂為多層,最多三層。
舉例:使用者輸入rowkey為'fb',通過二分查詢找到‘fb’是在‘a’和‘m’,將索引a的索引塊載入到記憶體中,通過二分查詢定位到fb在index 'd'和'h'之間,接下來訪問索引'd'指向的葉子節點。將索引'd'指向的中間節點索引塊載入到記憶體,通過二分查詢定位找到fb在index 'f'和'g'之間,最後需要訪問索引'f'指向的Data Block節點。將索引'f'指向的Data Block載入到記憶體,通過遍歷的方式找到對應KeyValue。所以一次查詢的IO正常為3次。
2. BlockCache中檢索目標Block
Block快取到Block Cache之後會構建一個Map,Map的Key是BlockKey,Value是Block在記憶體中的地址。
3. 從Block中讀取待查詢KeyValueHFile Block由KeyValue(由小到大依次儲存)構成,但這些KeyValue並不是固定長度的,只能遍歷掃描查詢。
4.深入理解Coprocessor(協處理器)
對於一些特殊業務,需要從Hbase中載入大量資料進行求和,求平均等聚合運算,會導致
- 大量資料傳輸可能會成為瓶頸
- 客戶端OOM
- 大量資料傳輸可能將叢集頻寬耗盡,影響叢集讀寫
需要將客戶端計算程式碼遷移到RegionServer上執行。
4.1 Coprocessor的分類
HBase Coprocessor分為兩種:Observer和Endpoint
Observer Coprocessor類似於MySQL中的觸發器。ObserverCoprocessor提供鉤子使使用者程式碼在特定事件發生之前或者之後得到執行。
使用Observer Coprocessor的最典型案例是在執行put或者get等操作之前檢查使用者許可權。例如Ranger整合Hbase。Hbase中的配置檔案如下:
<property> <name>hbase.coprocessor.master.classes</name> <value>org.apache.ranger.authorization.hbase.RangerAuthorizationCoprocessor</value> </property> <property> <name>hbase.coprocessor.region.classes</name> <value>org.apache.ranger.authorization.hbase.RangerAuthorizationCoprocessor</value> </property>
Endpoint Coprocessor類似於MySQL中的儲存過程。允許將使用者程式碼下推到資料層執行。一個典型的例子就是上文提到的計算一張表(設計大量Region)的平均值或者求和,可以使用Endpoint Coprocessor將計算邏輯下推到RegionServer執行。
4.2 coprocessor載入
使用者定義的Coprocessor可以通過兩種方式載入到RegionServer :一種是通過配置檔案靜態載入;一種是動態載入。
1. 靜態載入 ---- 通過修改hbase-site.xml檔案,然後將jar放到hbase的lib檔案中,類似於ranger。
2. 動態載入 ---- 動態載入不需要重啟叢集,通過shell來載入,可以只對某一張表。