天天看點

HBase原理-RegionServer當機資料恢複

HBase采用類LSM的架構體系,資料寫入并沒有直接寫入資料檔案,而是會先寫入緩存(Memstore),在滿足一定條件下緩存資料再會異步重新整理到硬碟。為了防止資料寫入緩存之後不會因為RegionServer程序發生異常導緻資料丢失,在寫入緩存之前會首先将資料順序寫入HLog中。如果不幸一旦發生RegionServer當機或者其他異常,這種設計可以從HLog中進行日志回放進行資料補救,保證資料不丢失。HBase故障恢複的最大看點就在于如何通過HLog回放補救丢失資料。

為了更好的了解HBase故障恢複原理,需要對HLog有簡單的認識。HLog的整個生命曆程可以使用下面一張圖來表示:

HBase原理-RegionServer當機資料恢複
HBase原理-RegionServer當機資料恢複

上圖可以看出,一個HLog由RegionServer上所有Region的日志資料構成,日志資料的最小單元為<HLogKey,WALEdit>,其中HLogKey由sequenceid、writetime、clusterid、regionname以及tablename組成。其中sequenceid是日志寫入時配置設定給資料的一個自增數字,先寫入的日志資料sequenceid小,後寫入的sequenceid大。

2. HLog滾動:HBase背景啟動了一個線程會每隔一段時間(由參數’hbase.regionserver.logroll.period’決定,預設1小時)進行日志滾動,即新生成一個新的日志檔案。可見,HLog日志檔案并不是一個大檔案,而是會産生很多小檔案。有些同學就會問了,為什麼需要産生多個日志檔案,一個日志檔案不好嗎?這是因為随着資料的不斷寫入,HLog所占空間将會變的越來越大,然而很多日志資料其實已經沒有任何作用了,這部分資料完全可以被删掉。而删除資料最好是一個檔案一個檔案整體删除,是以設計了日志滾動機制,友善檔案整體删除。類似于Binlog的處理機制。

3. HLog失效:上文提到,很多日志資料在之後會因為失效進而可以被删除,并且删除操作是以檔案為單元執行的。那怎麼判斷一個日志檔案裡面的資料失效了呢?首先從原理上講一旦資料從Memstore中落盤,對應的日志就可以被删除,是以一個檔案所有資料失效,隻需要看該檔案中最大sequenceid對應的資料是否已經落盤就可以,HBase會在每次執行flush的時候紀錄對應的最大的sequenceid,如果前者小于後者,則可以認為該日志檔案失效。一旦判斷失效就會将該檔案從WALs檔案夾移動到OldWALs檔案夾。

4. HLog删除:HMaster背景會啟動一個線程每隔一段時間(由參數’hbase.master.cleaner.interval’,預設1分鐘)會檢查一次檔案夾OldWALs下的所有失效日志檔案,确認是否可以被删除,确認之後執行删除操作。又有同學問了,剛才不是已經确認可以被删除了嗎?這裡基于兩點考慮,第一對于使用HLog進行主從複制的業務來說,第三步的确認并不完整,需要繼續确認是否該HLog還在應用于主從複制;第二對于沒有執行主從複制的業務來講,HBase依然提供了一個過期的TTL(由參數’hbase.master.logcleaner.ttl’決定,預設10分鐘),也就是說OldWALs裡面的檔案最多依然再儲存10分鐘。

HBase的故障恢複我們都以RegionServer當機恢複為例,引起RegionServer當機的原因各種各樣,有因為Full GC導緻、網絡異常導緻、官方Bug導緻(close wait端口未關閉)以及DataNode異常導緻等等。

這些場景下一旦RegionServer發生當機,HBase都會馬上檢測到這種當機,并且在檢測到當機之後會将當機RegionServer上的所有Region重新配置設定到叢集中其他正常RegionServer上去,再根據HLog進行丢失資料恢複,恢複完成之後就可以對外提供服務,整個過程都是自動完成的,并不需要人工介入。基本原理如下圖所示:

HBase原理-RegionServer當機資料恢複

HBase檢測當機是通過Zookeeper實作的, 正常情況下RegionServer會周期性向Zookeeper發送心跳,一旦發生當機,心跳就會停止,超過一定時間(SessionTimeout)Zookeeper就會認為RegionServer當機離線,并将該消息通知給Master。上述步驟中比較特殊的是HLog切分,其他步驟相信都能夠了解,為什麼需要切分HLog?大家都知道目前(0.98)版本中一台RegionServer隻有一個HLog檔案,即所有Region的日志都是混合寫入該HLog的,然而,回放日志是以Region為單元進行的,一個Region一個Region回放,是以在回放之前首先需要将HLog按照Region進行分組,每個Region的日志資料放在一起,友善後面按照Region進行回放。這個分組的過程就稱為HLog切分。

根據實作方式的不同,HBase的故障恢複前後經曆了三種不同模式,如下圖所示,下面會針對每一種模式進行詳細介紹:

HBase原理-RegionServer當機資料恢複

HBase的最初階段是使用如下流程進行日志切分的,整個過程都由HMaster控制執行。如下圖所示:

HBase原理-RegionServer當機資料恢複

1. 将待切分日志檔案夾重命名,為什麼需要将檔案夾重命名呢?這是因為在某些場景下RegionServer并沒有真正當機,但是HMaster會認為其已經當機并進行故障恢複,比如最常見的RegionServer發生長時間Full GC,這種場景下使用者并不知道RegionServer當機,所有的寫入更新操作還會繼續發送到該RegionServer,而且由于該RegionServer自身還繼續工作是以會接收使用者的請求,此時如果不重命名日志檔案夾,就會發生HMaster已經在使用HLog進行故障恢複了,但是RegionServer還在不斷寫入HLog

2. 啟動一個讀線程依次順序讀出每個HLog中所有<HLogKey,WALEdit>資料對,根據HLogKey所屬的Region不同寫入不同的記憶體buffer中,如上圖Buffer-Region1記憶體存放Region1對應的所有日志資料,這樣整個HLog所有資料會被完整group到不同的buffer中

3. 每個buffer會對應啟動一個寫線程,負責将buffer中的資料寫入hdfs中(對應的路徑為/hbase/table_name/region/recoverd.edits/.tmp),再等Region重新配置設定到其他RegionServer之後按順序回放對應Region的日志資料。

這種日志切分可以完成最基本的任務,但是效率極差,在某些場景下(叢集整體當機)進行恢複可能需要N個小時!也因為恢複效率太差,是以開發了Distributed Log Splitting架構。

Distributed Log Splitting是Log Splitting的分布式實作,它借助Master和所有RegionServer的計算能力進行日志切分,其中Master作為協調者,RegionServer作為實際的工作者。基本工作原理如下圖所示:

HBase原理-RegionServer當機資料恢複

1. Master會将待切分日志路徑釋出到Zookeeper節點上(/hbase/splitWAL),每個日志作為一個任務,每個任務都會有對應狀态,起始狀态為TASK_UNASSIGNED

2. 所有RegionServer啟動之後都注冊在這個節點上等待新任務,一旦Master釋出任務之後,RegionServer就會搶占該任務

3. 搶占任務實際上首先去檢視任務狀态,如果是TASK_UNASSIGNED狀态,說明目前沒有人占有,此時就去修改該節點狀态為TASK_OWNED。如果修改失敗,說明其他RegionServer也在搶占,修改成功表明任務搶占成功。

4. RegionServer搶占任務成功之後會分發給相應線程處理,如果處理成功,會将該任務對應zk節點狀态修改為TASK_DONE,一旦失敗會修改為TASK_ERR

5. Master會一直監聽在該ZK節點上,一旦發生狀态修改就會得到通知。任務狀态變更為TASK_ERR的話,Master會重新釋出該任務,而變更為TASK_DONE的話,Master會将對應的節點删除

下圖是RegionServer搶占任務以及搶占任務之後的工作流程:

HBase原理-RegionServer當機資料恢複

1. 假設Master目前釋出了4個任務,即目前需要回放4個日志檔案,分别為hlog1、hlog2、hlog3和hlog4

2. RegionServer1搶占到了hlog1和hlog2日志,RegionServer2搶占到了hlog3日志,RegionServer3搶占到了hlog4日志

3. 以RegionServer1為例,其搶占到hlog1和hlog2日志之後會分别分發給兩個HLogSplitter線程進行處理,HLogSplitter負責對日志檔案執行具體的切分,切分思路還是首先讀出日志中每一個<HLogKey, WALEdit>資料對,根據HLogKey所屬Region寫入不同的Region Buffer

4. 每個Region Buffer都會有一個對應的寫線程,将buffer中的日志資料寫入hdfs中,寫入路徑為/hbase/table/region2/seqenceid.temp,其中seqenceid是一個日志中某個region對應的最大sequenceid

5. 針對某一region回放日志隻需要将該region對應的所有檔案按照sequenceid由小到大依次進行回放即可

這種Distributed Log Splitting方式可以很大程度上加快整個故障恢複的程序,正常故障恢複時間可以降低到分鐘級别。然而,這種方式會産生很多日志小檔案,産生的檔案數将會是M * N,其中M是待切分的總hlog數量,N是一個當機RegionServer上的Region個數。假如一個RegionServer上有200個Region,并且有90個hlog日志,一旦該RegionServer當機,那這種方式的恢複過程将會建立 90 * 200 = 18000個小檔案。這還隻是一個RegionServer當機的情況,如果是整個叢集當機小檔案将會更多!!!

該方案在基本流程上做了一些改動,如下圖所示:

HBase原理-RegionServer當機資料恢複

相比Distributed Log Splitting方案,流程上的改動主要有兩點:先重新配置設定Region,再切分回放HLog;Region重新配置設定打開之後狀态設定為recovering,核心在于recovering狀态的Region可以對外提供寫服務,不能提供讀服務,而且不能執行split、merge等操作。

DLR的HLog切分回放基本架構類似于Distributed Log Splitting,但它在分解完HLog為Region-Buffer之後并沒有去寫入小檔案,而是直接去執行回放。這樣設計可以大大減少小檔案的讀寫IO消耗,解決DLS的切身痛點。

可見,在寫可用率以及恢複性能上,DLR要遠遠優于DLS方案,官方也給出了一個簡單的測試報告:

HBase原理-RegionServer當機資料恢複

可見,DLR在寫可用恢複是最快的,讀可用恢複稍微弱一點,但都比DLS好很多

了解了DLR的解決方案後,再回頭看DLS的實作方案,就不禁要問上一句:為什麼DLS需要将Buffer中的資料寫入小檔案中?個人認為原因最有可能是寫入小檔案之後,同一個Region的不同日志資料可以按照檔案名中sequenceid由小到大進行順序回放,完全可以模拟當時資料寫入的流程,不會有絲毫偏差。而如果不寫小檔案,很難在分布式環境下對sequenceid進行排序,這裡就有一個問題,不按順序對HLog進行回放會不會出現問題?這個問題可以分為下面兩個層面進行讨論:

1. 不同時間更新的相同rowkey,不按順序回放會不會有問題?比如WAL1中有<rowkey, t1>,WAL2中有<rowkey,t2>,正常情況下應該先回放<rowkey,t1>對應的日志資料,再回放<rowkey,t2>對應的日志資料,如果順序颠倒會不會有問題?

第一眼看到這樣的問題覺得一定不行啊,正常情況下<rowkey,t2>對應的日志資料才是最後的真正資料,一旦颠倒之後不就變成<rowkey,t1>對應的日志資料了。

這裡需要關注更新時間的概念,rowkey回放時,如果寫入時間戳定義為回放時間的話,肯定會有異常的。但是如果回放日志資料的時候rowkey寫入時間戳被定義為當時rowkey資料寫入日志的時間的話,就正常了。按照上面例子,順序即使颠倒,先寫<rowkey,t2>再寫<rowkey,t1>,但是寫入資料的時間戳(版本)依然保持不變時t2和t1的話,最大版本資料還是<rowkey,t2>,使用者讀取最新資料依然是<rowkey,t2>,和回放順序并沒有關系。

2. 相同時間戳更新的相同rowkey,不按順序回放會不會有問題?比如WAL1中有<rowkey, t0>,WAL2中有<rowkey,t0>,正常情況下也應該先回放前者,再回放後者,如果順序颠倒會不會有問題?

為什麼同一時間會有多條相同rowkey的寫入更新,而且還在不同的日志檔案中?大家肯定會有這樣的疑問。問題中‘同一時間’的機關是ms,在很多寫入吞吐量很大的場景下同一毫秒寫入大量資料并不是不可能,那先後寫入兩條相同rowkey的資料也必然可能,至于為什麼在不同檔案,假如剛好第一次更新完rowkey的時候日志截斷了,第二次更新就會落入下一個日志。

那這種情況下前後兩次更新時間戳還一緻,颠倒順序就辦法分出哪個版本大了呀!莫慌,不是還有sequenceid~,隻要在回放的時候将日志資料原生sequenceid也一同寫入,不就可以在時間戳相同的情況下根據sequenceid進行判斷了。具體實作隻需要寫入一個replay标示(用來表示該資料時回放寫入)和相應的sequenceid,使用者在讀取的時候如果遇到兩個都帶有replay标示而且rowkey、cf、column、時間戳都相同的情況,還需要比較sequenceid就可以分辨出來哪個資料版本更大。

在0.95版本DLR功能已經基本實作,一度在0.99版本已經設為預設,但是因為還是有一些功能性缺陷(主要是在rolling upgrades的場景下可能會導緻資料丢失),又在1.1版本取消了預設設定。使用者可以通過設定參數hbase.master.distributed.log.replay = true 來開啟DLR功能,當然前提是将HFile格式設定為V3(v3格式HFile引入了tag功能,replay标示就是用tag進行實作的)

本文主要介紹了HLog相關知識,同時基于此對HBase中RegionServer當機之後整個恢複流程以及原理進行了深入分析,重點分析了DLS方案以及DLR方案,希望和大家一起學習HBase故障恢複子產品知識。文中如有錯誤,可以一起讨論學習。

本文轉載自:http://hbasefly.com

<a href="http://hbasefly.com/2016/10/29/hbase-regionserver-recovering/" target="_blank">原文連結</a>