1. 程式人生 > >HBase最佳實踐 – Scan用法大觀園

HBase最佳實踐 – Scan用法大觀園

HBase從用法的角度來講其實乏陳可善,所有更新插入刪除基本一兩個API就可以搞定,要說稍微有點複雜的話,Scan的用法可能會多一些說頭。而且經過筆者觀察,很多業務對Scan的用法可能存在一些誤區(對於這些誤區,筆者也會在下文指出),因此有了本篇文章的寫作動機。也算是Scan系列的其中一篇吧,後面對於Scan還會有一篇結合HDFS分析HBase資料讀取在HDFS層面是怎麼一個流程,敬請期待。

HBase中Scan從大的層面來看主要有三種常見用法:ScanAPI、TableScanMR以及SnapshotScanMR。三種用法的原理不盡相同,掃描效率也當然相差甚多,最重要的是這幾種用法適用於不同的應用場景,業務需要根據自己的使用場景選擇合適的掃描方式。接下來分別對這三種用法從工作原理、最佳實踐兩個層面進行解析,最後再縱向對三種用法進行一下對比,希望大家能夠從用法層面對Scan有更多瞭解。

ScanAPI

scan客戶端設計原理

最常見的scan用法,見官方API文件。scan的原理之前在多篇文章中都有提及,為了表述方便,有必要在此簡單概述一番。HBase中scan並不像大家想象的一樣直接傳送一個命令過去,伺服器就將滿足掃描條件的所有資料一次性返回給客戶端。而實際上它的工作原理如下圖所示:

1

上圖右側是HBase scan的客戶端程式碼,其中for迴圈中每次遍歷ResultScanner物件獲取一行記錄,實際上在客戶端層面都會呼叫一次next請求。next請求整個流程可以分為如下幾個步驟:

  1. next請求首先會檢查客戶端快取中是否存在還沒有讀取的資料行,如果有就直接返回,否則需要將next請求給HBase伺服器端(RegionServer)。
  2. 如果客戶端快取已經沒有掃描結果,就會將next請求傳送給HBase伺服器端。預設情況下,一次next請求僅可以請求100行資料(或者返回結果集總大小不超過2M)
  3. 伺服器端接收到next請求之後就開始從BlockCache、HFile以及memcache中一行一行進行掃描,掃描的行數達到100行之後就返回給客戶端,客戶端將這100條資料快取到記憶體並返回一條給上層業務。

上層業務不斷一條一條獲取掃描資料,在資料量大的情況下實際上HBase客戶端會不斷髮送next請求到HBase伺服器。有的朋友可能會問為什麼scan需要設計為多次next請求的模式?個人認為這是基於多個層面的考慮:

  1. HBase本身儲存了海量資料,所以很多場景下一次scan請求的資料量都會比較大。如果不限制每次請求的資料集大小,很可能會導致系統頻寬吃緊從而造成整個叢集的不穩定。
  2. 如果不限制每次請求的資料集大小,很多情況下可能會造成客戶端快取OOM掉。
  3. 如果不限制每次請求的資料集大小,很可能伺服器端掃描大量資料會花費大量時間,客戶端和伺服器端的連線就會timeout。

這樣的設計有沒有瑕疵?

next策略可以避免在大資料量的情況下發生各種異常情況,但這樣的設計對於掃描效率似乎並不友好,這裡舉兩個例子:

  1. scan並沒有併發執行。這裡可能很多看官會問:掃描資料分佈在不同的region難道也不會並行執行掃描嗎?是的,確實不會,至少在現在的版本中沒有實現。這點一定出乎很多讀者的意料,我們知道get的批量讀請求會將所有的請求按照目標region進行分組,不同分組的get請求會併發執行讀取。然而scan並沒有這樣實現。
  2. 大家有沒有注意到上圖中步驟3和步驟4之間HBase伺服器端掃描資料的時候HBase客戶端在幹什麼?阻塞等待是吧。確實,所以從客戶端視角來看整個掃描時間=客戶端處理資料時間+伺服器端掃描資料時間,這能不能優化?

ScanAPI應用場景

根據上面的分析,scan API的效率很大程度上取決於掃描的資料量。通常建議OLTP業務中少量資料量掃描的scan可以使用scan API,大量資料的掃描使用scan API,掃描效能有時候並不能夠得到有效保證。

ScanAPI最佳實踐

  1. 批量OLAP掃描業務建議不要使用ScanAPI,ScanAPI適用於少量資料掃描場景(OLTP場景)
  2. 建議所有scan儘可能都設定startkey以及stopkey減少掃描範圍
  3. 建議所有僅需要掃描部分列的scan儘可能通過介面setFamilyMap設定列族以及列

TableScanMR

ScanAPI僅適用於OLTP場景,那OLAP場景下需要從HBase中掃描大量資料進行分析怎麼辦呢?現在有很多業務需求都需要從HBase掃描大量資料進行分析,比如最常見的使用者行為分析業務,通常需要掃描某些使用者最近一段時間的網路行為資料進行分析。

對於這類業務,HBase目前提供了兩種基於MR掃描的用法,分別為TableScanMR以及SnapshotScanMR。首先來介紹TableScanMR,具體用法可以參考官方文件。TableScanMR的工作原理其實很簡單,說白了就是ScanAPI的並行化。如下圖所示:

2

TableScanMR會將scan請求根據目標region的分界進行分解,分解成多個sub-scan,每個sub-scan本質上就是一個ScanAPI。假如scan是全表掃描,那這張表有多少region,就會將這個scan分解成多個sub-scan,每個sub-scan的startkey和stopkey就是region的startkey和stopkey。

TableScanMR最佳實踐

  1. TableScanMR設計為OLAP場景使用,因此在離線掃描時儘可能使用該中方式
  2. TableScanMR原理上主要實現了ScanAPI的並行化,將scan按照region邊界進行切分。這種場景下整個scan的時間基本等於最大region掃描的時間。在某些有資料傾斜的場景下可能出現某一個region上有大量待掃描資料,而其他大量region上都僅有很少的待掃描資料。這樣並行化效果並不好。針對這種資料傾斜的場景TableScanMR做了平衡處理,它會將大region上的scan切分成多個小的scan使得所有分解後的scan掃描的資料量基本相當。這個優化預設是關閉的,需要設定引數”hbase.mapreduce.input.autobalance”為true。因此建議大家使用TableScanMR時將該引數設定為true。
  3. 儘量將掃描表中相鄰的小region合併成大region,而將大region切分成稍微小點的region
  4. TableScanMR中Scan需要注意如下兩個引數設定:
Scan scan = new Scan();
scan.setCaching(500);        // 1 is the default in Scan, which will be bad for MapReduce jobs
scan.setCacheBlocks(false);  // don't set to true for MR jobs

SnapshotScanMR

SnapshotScanMR與TableScanMR相同都是使用MR並行化對資料進行掃描,兩者用法也基本相同,直接使用TableScanMR的用法,在此基礎上做部分修改即可,如下所示:

3

但兩者在實現上卻有多個非常大的區別:

  1. 從命名來看就知道,SnapshotScanMR掃描於原始表對應的snapshot之上(更準確來說根據snapshot restore出來的hfile),而TableScanMR掃描於原始表。
  2. SnapshotScanMR直接會在客戶端開啟region掃描HDFS上的檔案,不需要傳送Scan請求給RegionServer,再有RegionServer掃描HDFS上的檔案。是的,你沒看錯,是在客戶端直接掃描HDFS上的檔案,這類scanner稱之為ClientSideRegionScanner。

下圖是SnapshotScanMR的工作原理圖(注意和TableScanMR工作原理圖對比):

4

這是一個相對簡單的示意圖,其中省略了很多處理snapshot的過程以及切分scan的過程。總體來看和TableScanMR工作流程基本一致,最大的不同來自region掃描HDFS這個模組,TableScanMR中這個模組來自於regionserver,而SnapshotScanMR中客戶端直接繞過regionserver在客戶端借用region中的掃描機制直接掃描hdfs中資料。

有些朋友可能要問了,為什麼要這麼玩?總結起來,之所以這麼玩主要有兩個原因:

  1. 減小對RegionServer的影響。很顯然,SnapshotScanMR這種繞過RegionServer的實現方式最大限度的減小了對叢集中其他業務的影響。
  2. 極大的提升了掃描效率。SnapshotScanMR相比TableScanMR在掃描效率上會有2倍~N倍的效能提升(下一小節對各種掃描用法效能做個對比評估)。有人又要問了,為什麼會有這麼大的效能提升?個人認為主要有如下兩個方面的原因:
  • 掃描的過程少了一次網路傳輸,對於大資料量的掃描,網路傳輸花費的時間是非常龐大的,這主要可能牽扯到資料的序列化以及反序列化開銷。
  • TableScanMR掃描中RegionServer很可能會成為瓶頸,而SnapshotScanMR掃描並沒有這個瓶頸點。

在最後說一個TableScanMR和SnapshotScanMR都存在的問題,兩者實際上都是按照region對scan進行切分,然而對於很多大region(大於30g),單個region的掃描粒度還是太大。另外,很多scan掃描可能並沒有涉及多個region,而是集中在某一個region上,舉個例子,掃描某個使用者最近一個月的行為記錄,如果rowkey設計為username+timestamp的話,待掃描資料通常會集中儲存在一個region上,這種掃描如果使用MR的話,在當前的策略下只會生成一個Mapper。因此有必要提供一些其他策略可以將scan分解的粒度做的更細。

基本效能對比

針對TableScanMR和SnapshotScanMR兩種掃描方式,筆者做過一個簡單測試,同樣掃描1億條單行1K的記錄(region有15個),SnapshotScanMR所需要的時間基本是TableScanMR的一半。前些天筆者剛好看到一個分享,裡面有對使用ScanAPI、ClientSideRegionScannerAPI、TableScannMR以及SnapshotScanMR進行了效能對比,如下圖所示:

5

6

從上圖中可以看出,使用ScanAPI的效能最差,SnapshotScanMR的效能最好。SnapshotScanMR的效能相比TableScanMR(ScanMR)也有3倍的效能提升。然而在實際應用中,和小米committer爭神之前聊過,SnapshotScanMR目前可能還有很多不是很完善的地方,他們也在不斷的修復,相信在之後的版本中SnapshotScanMR會更加成熟。