HBase原理 – 解析Region切分的所有細節

Region自動切分是HBase能夠擁有良好擴張性的最重要因素之一,也必然是所有分佈式系統追求無限擴展性的一副良藥。HBase系統中Region自動切分是如何實現的?這裏面涉及很多知識點,比如Region切分的觸發條件是什麼?Region切分的切分點在哪裏?如何切分才能最大的保證Region的可用性?如何做好切分過程中的異常處理?切分過程中要不要將數據移動?等等,這篇文章將會對這些細節進行基本的說明,一方面可以讓大家對HBase中Region自動切分有更加深入的理解,另一方面如果想實現類似的功能也可以參考HBase的實現方案。

Region切分觸發策略

在最新穩定版(1.2.6)中,HBase已經有多達6種切分觸發策略。當然,每種觸發策略都有各自的適用場景,用戶可以根據業務在表級別選擇不同的切分觸發策略。常見的切分策略如下圖:

HBase原理 – 解析Region切分的所有細節

• ConstantSizeRegionSplitPolicy:0.94版本前默認切分策略。這是最容易理解但也最容易產生誤解的切分策略,從字面意思來看,當region大小大於某個閾值(hbase.hregion.max.filesize)之後就會觸發切分,實際上並不是這樣,真正實現中這個閾值是對於某個store來說的,即一個region中最大store的大小大於設置閾值之後纔會觸發切分。另外一個大家比較關心的問題是這裏所說的store大小是壓縮後的文件總大小還是未壓縮文件總大小,實際實現中store大小爲壓縮後的文件大小(採用壓縮的場景)。ConstantSizeRegionSplitPolicy相對來來說最容易想到,但是在生產線上這種切分策略卻有相當大的弊端:切分策略對於大表和小表沒有明顯的區分。閾值(hbase.hregion.max.filesize)設置較大對大表比較友好,但是小表就有可能不會觸發分裂,極端情況下可能就1個,這對業務來說並不是什麼好事。如果設置較小則對小表友好,但一個大表就會在整個集羣產生大量的region,這對於集羣的管理、資源使用、failover來說都不是一件好事。

• I ncreasingToUpperBoundRegionSplitPolicy : 0.94版本~2.0版本默認切分策略。這種切分策略微微有些複雜,總體來看和ConstantSizeRegionSplitPolicy思路相同,一個region中最大store大小大於設置閾值就會觸發切分。但是這個閾值並不像ConstantSizeRegionSplitPolicy是一個固定的值,而是會在一定條件下不斷調整,調整規則和region所屬表在當前regionserver上的region個數有關係 :(#regions) (#regions) (#regions) flush size 2,當然閾值並不會無限增大,最大值爲用戶設置的MaxRegionFileSize。這種切分策略很好的彌補了ConstantSizeRegionSplitPolicy的短板,能夠自適應大表和小表。而且在大集羣條件下對於很多大表來說表現很優秀,但並不完美,這種策略下很多小表會在大集羣中產生大量小region,分散在整個集羣中。而且在發生region遷移時也可能會觸發region分裂。

• SteppingSplitPolicy: 2.0版本默認切分策略。這種切分策略的切分閾值又發生了變化,相比 IncreasingToUpperBoundRegionSplitPolicy 簡單了一些,依然和待分裂region所屬表在當前regionserver上的region個數有關係,如果region個數等於1,切分閾值爲flush size * 2,否則爲MaxRegionFileSize。這種切分策略對於大集羣中的大表、小表會比 IncreasingToUpperBoundRegionSplitPolicy 更加友好,小表不會再產生大量的小region,而是適可而止。

另外,還有一些其他分裂策略,比如使用DisableSplitPolicy:可以禁止region發生分裂;而KeyPrefixRegionSplitPolicy,DelimitedKeyPrefixRegionSplitPolicy對於切分策略依然依據默認切分策略,但對於切分點有自己的看法,比如KeyPrefixRegionSplitPolicy要求必須讓相同的PrefixKey待在一個region中。

在用法上,一般情況下使用默認切分策略即可,也可以在cf級別設置region切分策略,命令爲:

create ’table’, {NAME => ‘cf’, SPLIT_POLICY => ‘org.apache.hadoop.hbase.regionserver. ConstantSizeRegionSplitPolicy'}

Region切分準備工作-尋找SplitPoint

region切分策略會觸發region切分,切分開始之後的第一件事是尋找切分點-splitpoint。所有默認切分策略,無論是ConstantSizeRegionSplitPolicy、 IncreasingToUpperBoundRegionSplitPolicy 抑或是SteppingSplitPolicy,對於切分點的定義都是一致的。當然,用戶手動執行切分時是可以指定切分點進行切分的,這裏並不討論這種情況。

那切分點是如何定位的呢? 整個region中最大store中的最大文件中最中心的一個block的首個rowkey 。這是一句比較消耗腦力的語句,需要細細品味。另外,HBase還規定,如果定位到的rowkey是整個文件的首個rowkey或者最後一個rowkey的話,就認爲沒有切分點。

什麼情況下會出現沒有切分點的場景呢?最常見的就是一個文件只有一個block,執行split的時候就會發現無法切分。很多新同學在測試split的時候往往都是新建一張新表,然後往新表中插入幾條數據並執行一下flush,再執行split,奇蹟般地發現數據表並沒有真正執行切分。原因就在這裏,這個時候仔細的話你翻看debug日誌是可以看到這樣的日誌滴:

HBase原理 – 解析Region切分的所有細節

Region核心切分流程

HBase將整個切分過程包裝成了一個事務,意圖能夠保證切分事務的原子性。整個分裂事務過程分爲三個階段:prepare – execute – (rollback) ,操作模版如下:

HBase原理 – 解析Region切分的所有細節

• prepare階段:在內存中初始化兩個子region,具體是生成兩個HRegionInfo對象,包含tableName、regionName、startkey、endkey等。同時會生成一個transaction journal,這個對象用來記錄切分的進展,具體見rollback階段。

• execute階段:切分的核心操作。見下圖(來自 Hortonworks ):

HBase原理 – 解析Region切分的所有細節

  1. regionserver 更改ZK節點 /region-in-transition 中該region的狀態爲SPLITING。

  2. master通過watch節點/region-in-transition檢測到region狀態改變,並修改內存中region的狀態,在master頁面RIT模塊就可以看到region執行split的狀態信息。

  3. 在父存儲目錄下新建臨時文件夾.split保存split後的daughter region信息。

  4. 關閉parent region:parent region關閉數據寫入並觸發flush操作,將寫入region的數據全部持久化到磁盤。此後短時間內客戶端落在父region上的請求都會拋出異常NotServingRegionException。

  5. 核心分裂步驟:在.split文件夾下新建兩個子文件夾,稱之爲daughter A、daughter B,並在文件夾中生成reference文件,分別指向父region中對應文件。這個步驟是所有步驟中最核心的一個環節,生成reference文件日誌如下所示:

2017-08-12 11:53:38,158 DEBUG [StoreOpener-0155388346c3c919d3f05d7188e885e0-1] regionserver.StoreFileInfo: reference 'hdfs://hdfscluster/hbase-rsgroup/data/default/music/0155388346c3c919d3f05d7188e885e0/cf/d24415c4fb44427b8f698143e5c4d9dc.00bb6239169411e4d0ecb6ddfdbacf66' to region=00bb6239169411e4d0ecb6ddfdbacf66 hfile=d24415c4fb44427b8f698143e5c4d9dc。

其中reference文件名爲d24415c4fb44427b8f698143e5c4d9dc.00bb6239169411e4d0ecb6ddfdbacf66,格式看起來比較特殊,那這種文件名具體什麼含義呢?那來看看該reference文件指向的父region文件,根據日誌可以看到,切分的父region是00bb6239169411e4d0ecb6ddfdbacf66,對應的切分文件是d24415c4fb44427b8f698143e5c4d9dc,可見reference文件名是個信息量很大的命名方式,如下所示:

HBase原理 – 解析Region切分的所有細節

除此之外,還需要關注reference文件的文件內容,reference文件是一個引用文件(並非linux鏈接文件),文件內容很顯然不是用戶數據。文件內容其實非常簡單,主要有兩部分構成:其一是切分點 splitkey,其二是一個boolean類型的變量(true或者false),true表示該reference文件引用的是父文件的上半部分(top),而false表示引用的是下半部分 (bottom)。爲什麼存儲的是這兩部分內容?且聽下文分解。

看官可以使用hadoop命令 親自來查看reference文件的具體內容:

hadoop dfs -cat /hbase-rsgroup/data/default/music/0155388346c3c919d3f05d7188e885e0/cf/d24415c4fb44427b8f698143e5c4d9dc.00bb6239169411e4d0ecb6ddfdbacf66

  1. 父region分裂爲兩個子region後, 將daughter A、daughter B拷貝到HBase根目錄下,形成兩個新的region。

  2. parent region通知修改 hbase.meta 表後下線,不再提供服務。下線後parent region在meta表中的信息並不會馬上刪除,而是標註split列、offline列爲true,並記錄兩個子region。爲什麼不立馬刪除?且聽下文分解。
    HBase原理 – 解析Region切分的所有細節

  3. 開啓daughter A、daughter B兩個子region。通知修改 hbase.meta 表,正式對外提供服務。

    HBase原理 – 解析Region切分的所有細節

• rollback階段:如果execute階段出現異常,則執行rollback操作。爲了實現回滾,整個切分過程被分爲很多子階段,回滾程序會根據當前進展到哪個子階段清理對應的垃圾數據。代碼中使用 JournalEntryType 來表徵各個子階段,具體見下圖:

HBase原理 – 解析Region切分的所有細節

Region切分事務性保證

整個region切分是一個比較複雜的過程,涉及到父region中HFile文件的切分、兩個子region的生成、系統meta元數據的更改等很多子步驟,因此必須保證整個切分過程的事務性,即要麼切分完全成功,要麼切分完全未開始,在任何情況下也不能出現切分只完成一半的情況。

爲了實現事務性,hbase設計了使用狀態機(見SplitTransaction類)的方式保存切分過程中的每個子步驟狀態,這樣一旦出現異常,系統可以根據當前所處的狀態決定是否回滾,以及如何回滾。遺憾的是,目前實現中這些中間狀態都只存儲在內存中,因此一旦在切分過程中出現regionserver宕機的情況,有可能會出現切分處於中間狀態的情況,也就是RIT狀態。這種情況下需要使用hbck工具進行具體查看並分析解決方案。在2.0版本之後,HBase實現了新的分佈式事務框架Procedure V2(HBASE-12439),新框架將會使用HLog存儲這種單機事務(DDL操作、Split操作、Move操作等)的中間狀態,因此可以保證即使在事務執行過程中參與者發生了宕機,依然可以使用HLog作爲協調者對事務進行回滾操作或者重試提交,大大減少甚至杜絕RIT現象。這也是是2.0在可用性方面最值得期待的一個亮點!!!

Region切分對其他模塊的影響

通過region切分流程的瞭解,我們知道整個region切分過程並沒有涉及數據的移動,所以切分成本本身並不是很高,可以很快完成。切分後子region的文件實際沒有任何用戶數據,文件中存儲的僅是一些元數據信息-切分點rowkey等,那通過引用文件如何查找數據呢?子region的數據實際在什麼時候完成真正遷移?數據遷移完成之後父region什麼時候會被刪掉?

  1. 通過reference文件如何查找數據?

這裏就會看到reference文件名、文件內容的實際意義啦。整個流程如下圖所示:

HBase原理 – 解析Region切分的所有細節

(1)根據reference文件名(region名+真實文件名)定位到真實數據所在文件路徑

(2)定位到真實數據文件就可以在整個文件中掃描待查KV了麼?非也。因爲reference文件通常都只引用了數據文件的一半數據,以切分點爲界,要麼上半部分文件數據,要麼下半部分數據。那到底哪部分數據?切分點又是哪個點?還記得上文又提到reference文件的文件內容吧,沒錯,就記錄在文件中。

  1. 父region的數據什麼時候會遷移到子region目錄?

答案是子region發生major_compaction時。我們知道compaction的執行實際上是將store中所有小文件一個KV一個KV從小到大讀出來之後再順序寫入一個大文件,完成之後再將小文件刪掉,因此compaction本身就需要讀取並寫入大量數據。子region執行major_compaction後會將父目錄中屬於該子region的所有數據讀出來並寫入子region目錄數據文件中。可見將數據遷移放到compaction這個階段來做,是一件順便的事。

  1. 父region什麼時候會被刪除?

實際上HMaster會啓動一個線程定期遍歷檢查所有處於splitting狀態的父region,確定檢查父region是否可以被清理。檢測線程首先會在meta表中揪出所有split列爲true的region,並加載出其分裂後生成的兩個子region(meta表中splitA列和splitB列), 只需要檢查此兩個子region是否還存在引用文件,如果都不存在引用文件就可以認爲該父region對應的文件可以被刪除。現在再來看看上文中父目錄在meta表中的信息,就大概可以理解爲什麼會存儲這些信息了:
HBase原理 – 解析Region切分的所有細節

  1. split模塊在生產線的一些坑?

有些時候會有同學反饋說集羣中部分region處於長時間RIT,region狀態爲spliting。通常情況下都會建議使用hbck看下什麼報錯,然後再根據hbck提供的一些工具進行修復,hbck提供了部分命令對處於split狀態的rit region進行修復,主要的命令如下:

-fixSplitParents Try to force offline split parents to be online.
-removeParents Try to offline and sideline lingering parents and keep daughter regions.
-fixReferenceFiles Try to offline lingering reference store files

其中最常見的問題是 :

ERROR: Found lingering reference file hdfs://mycluster/hbase/news_user_actions/3b3ae24c65fc5094bc2acfebaa7a56de/meta/0f47cda55fa44cf9aa2599079894aed6.b7b3faab86527b88a92f2a248a54d3dc”

簡單解釋一下,這個錯誤是說reference文件所引用的父region文件不存在了,如果查看日誌的話有可能看到如下異常:

java.io.IOException: java.io.IOException: java.io.FileNotFoundException: File does not exist:/hbase/news_user_actions/b7b3faab86527b88a92f2a248a54d3dc/meta/0f47cda55fa44cf9aa2599079894aed

父region文件爲什麼會莫名其妙不存在?經過和朋友的討論,確認有可能是因爲官方bug導致,詳見HBASE-13331。這個jira是說HMaster在確認父目錄是否可以被刪除時,如果檢查引用文件(檢查是否存在、檢查是否可以正常打開)拋出IOException異常,函數就會返回沒有引用文件,導致父region被刪掉。正常情況下應該保險起見返回存在引用文件,保留父region,並打印日誌手工介入查看。如果大家也遇到類似的問題,可以看看這個問題,也可以將修復patch打到線上版本或者升級版本。

結語

感謝您的觀看,如有不足之處,歡迎批評指正。

爲了幫助大家讓學習變得輕鬆、高效,給大家免費分享一大批資料,幫助大家在成爲大數據工程師,乃至架構師的路上披荊斬棘。在這裏給大家推薦一個大數據學習交流圈:658558542 歡迎大家進×××流討論,學習交流,共同進步。

當真正開始學習的時候難免不知道從哪入手,導致效率低下影響繼續學習的信心。

但最重要的是不知道哪些技術需要重點掌握,學習時頻繁踩坑,最終浪費大量時間,所以有有效資源還是很有必要的。

最後祝福所有遇到瓶疾且不知道怎麼辦的大數據程序員們,祝福大家在往後的工作與面試中一切順利。

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