hbase讀寫流程分析

前言

最近被大佬問到一個問題,hbase查詢數據在最壞的場景下須要進行幾回rpc,當時就懵了..下面主要對client端代碼進行分析。閱讀文章和看源碼更配~html

 

讀數據

流程總覽

1. 從zookeeper中獲取meta信息,並經過meta信息找到須要查找的table的startkey所在的region信息java

2. 和該region所在的regionserver進行rpc交互獲取result數據庫

3. region server查詢memstore(memstore是是一個按key排序的樹形結構的緩衝區),若是有該rowkey,則直接返回,若沒有進入步驟4數組

4. 查詢blockcache,若是有該rowkey,則直接返回,若沒有進入步驟5緩存

5. 查詢storefile,無論有沒有都直接返回優化

 

client代碼分析

hbase讀數據除了直接操做hfile以外有3個入口,get(),batch()和scan(),get()相對而言就比較簡單,找到對應的regionserver而後發rpc便可,batch()採用單rpc多action的策略流程和get()相似,下面主要對scan涉及的核心接口進行分析。核心接口有如下幾個日誌

Connection:負責和zk創建鏈接server

Table:負責維護相關對象htm

ResultScanner:負責給使用者遍歷紓解對象

Caller:負責調用Callable

Callable:客戶端和hbase交互的主要接口

 

Connection

默認的鏈接器是HConnectionImplementation,能夠經過配置`hbase.client.connection.impl`修改。核心思路是基於zk的watcher,保持長鏈接,而後獲取hbase元數據

 

Table

table經過Connection.getTable()實例化,默認的實現是HTable。這個類比較簡單,只是維護了針對hbase一張表所用到的對象。主要關注遍歷的方法,經過HTable.getScanner()實例化一個新的ResultScanner,使用者經過ResultScanner迭代器遍歷獲取result數據。

 

Scanner

client提供了4種scanner,參考HTable.getScanner(),1. ClientScanner,讀取result的流程須要3次rpc,openScanner,next和closeScanner;2. 針對小量數據優化的ClientSmallScanner,和ClientScanner的區別在於,將openScanner,next和closeScanner合併到一個rpc執行,官方建議拉取的數據在64KB以內能夠考慮用SmallScanner的方式;另外兩個是基於reversed配置,也就是倒序遍歷region,須要交換startkey和endkey的位置。ClientScanner是咱們最經常使用的Scanner,也是默認的Scanner,下面對其進行分析

  1. 在初始化的時候經過nextScanner()方法,實例化一個新的Callable對象,並調用其call()方法
  2. next()方法,當使用者不斷的調用next()時,ClientScanner()會先從緩存中找,是否還有result,若是還有那麼直接返回,若是緩存中沒有result,那麼調用loadCache()方法
  3. loadCache()方法,調用Callable.call(),獲取result數組。這裏的異常處理須要特別關注,若是是UnkonwnScannerException,那麼重試rpc直到本次scan超時,若是是OutOfOrderScannerNextException異常,scanner會重試rpc請求重複步驟3,若是已經重試過,那麼直接拋出異常。重試的超時時間的配置`hbase.client.scanner.timeout.period`,默認是60s
  4. 拉取到result後,ClientScanner會進行合併,這是因爲拉取到的result是部分的,不是完整的,說到底hbase是以Cell爲最小單位進行存儲或者傳輸的,要封裝成result的話就須要進行合併。合併完以後將result緩存在內存中,緩存策略基於caching和maxResultSize,caching表示hbase client最多能夠緩存在內存多少條數據,也就是多少個result;maxResultSize表示hbase client最多能夠緩存多少內存大小的result,也就是控制result佔用堆的大小
  5. 判斷是否還須要再拉取result,這裏有兩種拉取判斷,一種是以前的region拉取失敗,轉而拉取其replica,另外一種是調用rpc拉取下一組result。
  6. result達到內存限制或者數量(maxResultSize,caching)則返回

 

ScannerCallable

ClientScanne對應的Callable是ScannerCallable,也是最典型的Callable,下面對其核心方法進行分析

prepare()方法

  1. 核心功能是經過RPCRetryingCallerWithReadReplicas.getRegionLocations獲取待遍歷的table startkey的region,從而定位到region server。

核心call()方法

  1. 首次調用call(),client會發送一次開始rpc,高速region server本次scan開始了,這次rpc不獲取result,只生成scannerId,以後的rpc不須要再傳遞scan配置,這造成了一個會話的概念。
  2. 經過rpc controller獲取CellScanner,再轉換成Result數組,這裏參考`ResponseConverter.getResults`。注意,這裏因爲獲取的result是連續的,也就是說region server是有狀態的服務,client每次rpc都會帶上當前請求的序號,也就是nextCallSeq,這有的相似傳統數據庫中的分頁做用。當出現序號不匹配,region server會拋出異常。
  3. 若是須要關閉,那麼向region server發送close的rpc

 

總結

hbase-client的scan操做整體上能夠當作是兩層迭代器,面向使用者的Scanner以及面向region server的Callable。Callable負責從regionserver中獲取result,主要解決,Scanner負責整合result提供給使用者。這樣作的思路很明顯,數據大小是確定會大於內存的,經過迭代器接口,可讓使用者處理完以前的result再拉取其餘result,從而起到分頁的效果,這操做對使用者是透明的。若是須要詳細的scan日誌,能夠經過配置`hbase.client.log.scanner.activity`來打開開關,默認是false。

 

寫數據

流程總覽

  1. zookeeper中獲取meta信息,並經過meta信息找到須要查找的table的startkey所在的region信息(和讀數據類似)
  2. 將put緩存在內存中,參考`BufferedMutatorImpl`,並計算其內存大小(計算方式會考慮java對象佔用的大小,參考《java對象在內存的大小》),當超過`hbase.client.write.buffer`默認2097152字節也就是2MB,client會將put 經過rpc交給region server
  3. region server接收數據後分別寫到HLog和MemStore上一份
  4. MemStore達到一個閾值後則把數據刷成一個StoreFile文件。若MemStore中的數據有丟失,則能夠從HLog上恢復
  5. 當多個StoreFile文件達到必定的數量,會觸發Compact和Major Compaction操做,這裏不對compaction的細節作展開。
  6. 當Compact後,逐步造成愈來愈大的StoreFIle後,會觸發Split操做,把當前的StoreFile分紅兩個,這裏至關於把一個大的region分割成兩個region,細節也不展開了。

 

總結

對於scan操做而言,拿ClientScanner來講,一次「完整rpc」過程包含3次rpc,open,result和close。若是失敗了,region不可用或者在split,那麼client會重試新的一次「完整rpc」,那麼就是6次rpc。其餘操做會少一點,例如SmallClientScanner一次「完整rpc」只須要1次rpc,它把open,close集成到了一塊兒。hbase在client仍是花了很多心思的。

 

參考

HBase-1.3.1代碼

相關文章
相關標籤/搜索