hbase讀寫流程分析


前言

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

 

讀數據

流程總覽

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

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建立連接

Table:負責維護相關對象

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代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM