HBase bulkLoad時間都花在哪?

近期工作中使用hbase bulkload向hbase導入2TB數據(10000個hfiles),我們發現將hfiles加載到hbase 表的過程用了將近一個小時。這和我對bulk load過程的理解不太相符,在我的理解中,hbase bulkload並不會產生數據copy,數據導入通過hdfs的mv操作完成。那麼,

問題1 :是什麼操作消耗了一個小時呢?

另外,對bulkload一直有個疑問,

問題2 :基於某個hbase cluster中的表生成的hfiles能否導入到其他hbase cluster中的相同表(表名和列簇都相同)中,bulkload會自動處理兩個集羣中表的region分佈差異嗎?

帶着上述兩個問題閱讀了hbase中LoadIncrementalHFiles類的代碼,本文對此做個梳理。

run方法

LoadIncrementalHFiles的main函數調用run方法:

run方法做了三件事:

1. 初始化

2. 判斷要導入的表是否存在,不存在且參數create.table爲yes, 則創建該表;不存在且create.table不爲yes則拋出異常TableNotFoundException

3. 調用doBulkLoad

初始化

LoadIncrementalHFiles的初始化過程比較簡單,主要是對hbase admin等對象的初始化:

doBulkLoad

doBulkLoad方法執行以下步驟:

1. 創建線程池

創建用於bulkload的線程池, 線程池大小由參數hbase.loadincremental.threads.max控制,默認爲當前機器的core數量。源碼如下:

2. 初始化加載項隊列

遍歷指定目錄,爲每個hfile生成一個LoadQueueItem對象並添加到隊列中(下文我們稱此隊列爲LQI隊列,稱隊列中的元素爲LQI)中,該步驟由discoverLoadQueue方法完成。

單個hfile的大小不應超過HREGION_MAX_FILESIZE, 該值由參數hbase.hregion.max.filesize控制,默認爲10GB。

一個LQI代表一個加載項,LoadQueueItem類的源碼如下 :

需要加載的文件在HDFS上按照column family被分配在不同的子目錄下,每個子目錄下的一個文件就對應一個LQI。

discoverLoadQueue方法中調用了visitBulkHFiles方法遍歷hfile所在的HDFS目錄,visitBulkHFiles方法對每個hfile會做一系列validation :

過濾掉reference,link, 以'_'開頭的,以及非hfile格式的文件。

3. 檢查column family的有效性

在discoverLoadQueue完成對所有hfiles的遍歷後,會對queue中所有的items進行column family的check,如果存在某個item的column family不屬於目標表,則拋出異常:

4. 循環分組加載

while循環的每次迭代主要執行groupOrSplit和bulkLoad兩個phase的操作:

a) groupOrSplitPhase

把queue中的所有文件根據目標表的region metadata進行分組,把每個文件劃分到其所屬region。

如果某個hfile的[firstkey, lastkey]不在任何region的[starkey, endkey]範圍內,則將此hfile拆分成兩個文件(拆分後的文件後綴爲.top和.bottom),拆分的split key就是firstkey所在region的endkey。

拆分後得到的兩個hfile會被封裝成LQI再添加回LQI隊列,這就是爲什麼需要一個while循環判斷LQI隊列是否爲空。需注意,拆分後,第一個LQI肯定會在某個region範圍內(除非在下次迭代加載該LQI之前目標region又發生了split),第二個LQI有可能仍需拆分。

groupOrSplitPhase完成之後,所有可加載的LQI都會被放到regionGroups中。regionGroups是一個Multimap,key爲region的startkey,value爲對應的LQI,一個region可對應多個LQI。

b) bulkLoadPhase : 

對於regionGroups中的每個key(即region的startkey),調用方法tryAtomicRegionLoad將其對應的所有LQI加載到目標table中。如果加載失敗,則將failed LQI再加入到LQI隊列中,供下一循環檢測和加載。tryAtomicRegionLoad方法會連接hbase region server,發送SecureBulkLoadHFilesRequest請求。

groupOrSplit和bulkLoad的操作都是通過上面創建的線程池對所有hfile併發執行的。除了這兩個phase的操作外,while循環中還會檢測一些異常情況:

a) 對於doBulkLoad中while(!queue.isEmpty)循環,如果經過maxRetries次嘗試後,LQI隊列仍不爲空,則拋出異常。maxRetries由參數hbase.bulkload.retries.number控制,默認爲10 :

b) 經過groupOrSplitPhase後,如果regionGroups中單個region單個column family對應的hfile個數超過了maxFilesPerRegionPerFamily,則拋出異常:


maxFilesPerRegionPerFamily由參數hbase.mapreduce.bulkload.max.hfiles.perRegion.perFamily控制,默認爲32。

問題1解答

通過上面的分析,我們知道bulkload過程對hfile目錄進行遍歷,每個hfile都會進行一系列validation,生成LQI,最終調用tryAtomicRegionLoad進行加載。我們通過打印每個步驟的耗時,發現檢測hfile文件格式,即visitBulkHFiles中調用的isHFileFormat方法是主要的耗時步驟, 這是因爲visitBulkHFiles方法是在主線程串行執行的,我們有10000個文件,並且每次isHFileFormat都會讀取hfile的file trailer,所以累計時間很長。

我們通過添加一個hbase配置項hbase.client.bulk.load.validate.hfile.format來控制是否進行hfile格式檢測,當將其設置爲false時,加載2TB數據(10000個hfile)從之前的1個小時縮短爲10分鐘。繞過文件格式檢查的前提是我們確定hfile的format都是正確的。我們還可以通過減少hfile的個數來減少bulkload在客戶端運行的時間。還有一個可能的解決方案是將visitBulkHFiles方法修改成多線程執行,以後有時間可以嘗試一下。

問題2解答

答案是肯定的。如上文所述,bulkload會將hfile的[firstkey, lastkey]和目標表region的[startkey, endkey]進行匹配,如果匹配失敗則會進行文件拆分,所以不用擔心不同集羣表中region的差異。

總結

本文對hbase bulkload的客戶端過程進行了分析,詳述了hfile的遍歷,檢測,分組,拆分,加載等步驟,並對文中開頭提出的兩個問題進行了解答。

水平有限,若有誤解,望讀者指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章