目錄
- HBase的故障恢復(fù)有哪三種不同模式攀细?
- HBase日志切分方法泌枪?
- Distributed Log Replay解決了哪些問題缀蹄?
HBase采用類LSM的架構(gòu)體系尘分,數(shù)據(jù)寫入并沒有直接寫入數(shù)據(jù)文件糊治,而是會(huì)先寫入緩存(Memstore)唱矛,在滿足一定條件下緩存數(shù)據(jù)再會(huì)異步刷新到硬盤。為了防止數(shù)據(jù)寫入緩存之后不會(huì)因?yàn)镽egionServer進(jìn)程發(fā)生異常導(dǎo)致數(shù)據(jù)丟失井辜,在寫入緩存之前會(huì)首先將數(shù)據(jù)順序?qū)懭際Log中绎谦。如果不幸一旦發(fā)生RegionServer宕機(jī)或者其他異常,這種設(shè)計(jì)可以從HLog中進(jìn)行日志回放進(jìn)行數(shù)據(jù)補(bǔ)救粥脚,保證數(shù)據(jù)不丟失窃肠。HBase故障恢復(fù)的最大看點(diǎn)就在于如何通過HLog回放補(bǔ)救丟失數(shù)據(jù)。
HLog簡(jiǎn)介
為了更好的理解HBase故障恢復(fù)原理刷允,需要對(duì)HLog有簡(jiǎn)單的認(rèn)識(shí)冤留。HLog的整個(gè)生命歷程可以使用下面一張圖來(lái)表示:
1. HLog構(gòu)建:
HLog的結(jié)構(gòu)示意圖:
上圖可以看出碧囊,一個(gè)HLog由RegionServer上所有Region的日志數(shù)據(jù)構(gòu)成,日志數(shù)據(jù)的最小單元為<HLogKey,WALEdit>纤怒,其中HLogKey由sequenceid糯而、writetime、clusterid肪跋、regionname以及tablename組成歧蒋。其中sequenceid是日志寫入時(shí)分配給數(shù)據(jù)的一個(gè)自增數(shù)字,先寫入的日志數(shù)據(jù)sequenceid小州既,后寫入的sequenceid大谜洽。
2. HLog滾動(dòng):
HBase后臺(tái)啟動(dòng)了一個(gè)線程會(huì)每隔一段時(shí)間(由參數(shù)’hbase.regionserver.logroll.period’決定,默認(rèn)1小時(shí))進(jìn)行日志滾動(dòng)吴叶,即新生成一個(gè)新的日志文件阐虚。可見蚌卤,HLog日志文件并不是一個(gè)大文件实束,而是會(huì)產(chǎn)生很多小文件。有些同學(xué)就會(huì)問了逊彭,為什么需要產(chǎn)生多個(gè)日志文件咸灿,一個(gè)日志文件不好嗎?這是因?yàn)殡S著數(shù)據(jù)的不斷寫入侮叮,HLog所占空間將會(huì)變的越來(lái)越大避矢,然而很多日志數(shù)據(jù)其實(shí)已經(jīng)沒有任何作用了,這部分?jǐn)?shù)據(jù)完全可以被刪掉囊榜。而刪除數(shù)據(jù)最好是一個(gè)文件一個(gè)文件整體刪除审胸,因此設(shè)計(jì)了日志滾動(dòng)機(jī)制,方便文件整體刪除卸勺。類似于Binlog的處理機(jī)制砂沛。
3. HLog失效:
上文提到,很多日志數(shù)據(jù)在之后會(huì)因?yàn)槭нM(jìn)而可以被刪除曙求,并且刪除操作是以文件為單元執(zhí)行的碍庵。那怎么判斷一個(gè)日志文件里面的數(shù)據(jù)失效了呢?首先從原理上講一旦數(shù)據(jù)從Memstore中落盤圆到,對(duì)應(yīng)的日志就可以被刪除怎抛,因此一個(gè)文件所有數(shù)據(jù)失效,只需要看該文件中最大sequenceid對(duì)應(yīng)的數(shù)據(jù)是否已經(jīng)落盤就可以芽淡,HBase會(huì)在每次執(zhí)行flush的時(shí)候紀(jì)錄對(duì)應(yīng)的最大的sequenceid,如果前者小于后者豆赏,則可以認(rèn)為該日志文件失效挣菲。一旦判斷失效就會(huì)將該文件從WALs文件夾移動(dòng)到OldWALs文件夾富稻。
4. HLog刪除:
HMaster后臺(tái)會(huì)啟動(dòng)一個(gè)線程每隔一段時(shí)間(由參數(shù)’hbase.master.cleaner.interval’,默認(rèn)1分鐘)會(huì)檢查一次文件夾OldWALs下的所有失效日志文件白胀,確認(rèn)是否可以被刪除椭赋,確認(rèn)之后執(zhí)行刪除操作。又有同學(xué)問了或杠,剛才不是已經(jīng)確認(rèn)可以被刪除了嗎哪怔?這里基于兩點(diǎn)考慮,第一對(duì)于使用HLog進(jìn)行主從復(fù)制的業(yè)務(wù)來(lái)說(shuō)向抢,第三步的確認(rèn)并不完整认境,需要繼續(xù)確認(rèn)是否該HLog還在應(yīng)用于主從復(fù)制;第二對(duì)于沒有執(zhí)行主從復(fù)制的業(yè)務(wù)來(lái)講挟鸠,HBase依然提供了一個(gè)過期的TTL(由參數(shù)’hbase.master.logcleaner.ttl’決定叉信,默認(rèn)10分鐘),也就是說(shuō)OldWALs里面的文件最多依然再保存10分鐘艘希。
HBase故障恢復(fù)三部曲
HBase的故障恢復(fù)我們都以RegionServer宕機(jī)恢復(fù)為例硼身,引起RegionServer宕機(jī)的原因各種各樣,有因?yàn)镕ull GC導(dǎo)致覆享、網(wǎng)絡(luò)異常導(dǎo)致佳遂、官方Bug導(dǎo)致(close wait端口未關(guān)閉)以及DataNode異常導(dǎo)致等等。
這些場(chǎng)景下一旦RegionServer發(fā)生宕機(jī)撒顿,HBase都會(huì)馬上檢測(cè)到這種宕機(jī)丑罪,并且在檢測(cè)到宕機(jī)之后會(huì)將宕機(jī)RegionServer上的所有Region重新分配到集群中其他正常RegionServer上去,再根據(jù)HLog進(jìn)行丟失數(shù)據(jù)恢復(fù)核蘸,恢復(fù)完成之后就可以對(duì)外提供服務(wù)巍糯,整個(gè)過程都是自動(dòng)完成的,并不需要人工介入客扎∷盥停基本原理如下圖所示:
HBase檢測(cè)宕機(jī)是通過Zookeeper實(shí)現(xiàn)的, 正常情況下RegionServer會(huì)周期性向Zookeeper發(fā)送心跳徙鱼,一旦發(fā)生宕機(jī)宅楞,心跳就會(huì)停止,超過一定時(shí)間(SessionTimeout)Zookeeper就會(huì)認(rèn)為RegionServer宕機(jī)離線袱吆,并將該消息通知給Master厌衙。上述步驟中比較特殊的是HLog切分,其他步驟相信都能夠理解绞绒,為什么需要切分HLog婶希?大家都知道當(dāng)前(0.98)版本中一臺(tái)RegionServer只有一個(gè)HLog文件,即所有Region的日志都是混合寫入該HLog的蓬衡,然而喻杈,回放日志是以Region為單元進(jìn)行的彤枢,一個(gè)Region一個(gè)Region回放,因此在回放之前首先需要將HLog按照Region進(jìn)行分組筒饰,每個(gè)Region的日志數(shù)據(jù)放在一起缴啡,方便后面按照Region進(jìn)行回放。這個(gè)分組的過程就稱為HLog切分瓷们。
根據(jù)實(shí)現(xiàn)方式的不同业栅,HBase的故障恢復(fù)前后經(jīng)歷了三種不同模式,如下圖所示谬晕,下面會(huì)針對(duì)每一種模式進(jìn)行詳細(xì)介紹:
1. Log Splitting
HBase的最初階段是使用如下流程進(jìn)行日志切分的碘裕,整個(gè)過程都由HMaster控制執(zhí)行。如下圖所示:
將待切分日志文件夾重命名固蚤,為什么需要將文件夾重命名呢娘汞?這是因?yàn)樵谀承﹫?chǎng)景下RegionServer并沒有真正宕機(jī),但是HMaster會(huì)認(rèn)為其已經(jīng)宕機(jī)并進(jìn)行故障恢復(fù)夕玩,比如最常見的RegionServer發(fā)生長(zhǎng)時(shí)間Full GC你弦,這種場(chǎng)景下用戶并不知道RegionServer宕機(jī),所有的寫入更新操作還會(huì)繼續(xù)發(fā)送到該RegionServer燎孟,而且由于該RegionServer自身還繼續(xù)工作所以會(huì)接收用戶的請(qǐng)求禽作,此時(shí)如果不重命名日志文件夾,就會(huì)發(fā)生HMaster已經(jīng)在使用HLog進(jìn)行故障恢復(fù)了揩页,但是RegionServer還在不斷寫入HLog旷偿。
啟動(dòng)一個(gè)讀線程依次順序讀出每個(gè)HLog中所有<HLogKey,WALEdit>數(shù)據(jù)對(duì),根據(jù)HLogKey所屬的Region不同寫入不同的內(nèi)存buffer中爆侣,如上圖Buffer-Region1內(nèi)存存放Region1對(duì)應(yīng)的所有日志數(shù)據(jù)萍程,這樣整個(gè)HLog所有數(shù)據(jù)會(huì)被完整group到不同的buffer中。
每個(gè)buffer會(huì)對(duì)應(yīng)啟動(dòng)一個(gè)寫線程兔仰,負(fù)責(zé)將buffer中的數(shù)據(jù)寫入hdfs中(對(duì)應(yīng)的路徑為/hbase/table_name/region/recoverd.edits/.tmp)茫负,再等Region重新分配到其他RegionServer之后按順序回放對(duì)應(yīng)Region的日志數(shù)據(jù)。
這種日志切分可以完成最基本的任務(wù)乎赴,但是效率極差忍法,在某些場(chǎng)景下(集群整體宕機(jī))進(jìn)行恢復(fù)可能需要N個(gè)小時(shí)!也因?yàn)榛謴?fù)效率太差榕吼,所以開發(fā)了Distributed Log Splitting架構(gòu)饿序。
2. Distributed Log Splitting
Distributed Log Splitting是Log Splitting的分布式實(shí)現(xiàn),它借助Master和所有RegionServer的計(jì)算能力進(jìn)行日志切分羹蚣,其中Master作為協(xié)調(diào)者,RegionServer作為實(shí)際的工作者√呦唬基本工作原理如下圖所示:
Master會(huì)將待切分日志路徑發(fā)布到Zookeeper節(jié)點(diǎn)上(/hbase/splitWAL)戈抄,每個(gè)日志作為一個(gè)任務(wù),每個(gè)任務(wù)都會(huì)有對(duì)應(yīng)狀態(tài)划鸽,起始狀態(tài)為TASK_UNASSIGNED
所有RegionServer啟動(dòng)之后都注冊(cè)在這個(gè)節(jié)點(diǎn)上等待新任務(wù),一旦Master發(fā)布任務(wù)之后嫂用,RegionServer就會(huì)搶占該任務(wù)
搶占任務(wù)實(shí)際上首先去查看任務(wù)狀態(tài),如果是TASK_UNASSIGNED狀態(tài)嘱函,說(shuō)明當(dāng)前沒有人占有,此時(shí)就去修改該節(jié)點(diǎn)狀態(tài)為TASK_OWNED往弓。如果修改失敗,說(shuō)明其他RegionServer也在搶占蓄氧,修改成功表明任務(wù)搶占成功函似。
RegionServer搶占任務(wù)成功之后會(huì)分發(fā)給相應(yīng)線程處理,如果處理成功喉童,會(huì)將該任務(wù)對(duì)應(yīng)zk節(jié)點(diǎn)狀態(tài)修改為TASK_DONE撇寞,一旦失敗會(huì)修改為TASK_ERR
Master會(huì)一直監(jiān)聽在該ZK節(jié)點(diǎn)上,一旦發(fā)生狀態(tài)修改就會(huì)得到通知堂氯。任務(wù)狀態(tài)變更為TASK_ERR的話蔑担,Master會(huì)重新發(fā)布該任務(wù),而變更為TASK_DONE的話咽白,Master會(huì)將對(duì)應(yīng)的節(jié)點(diǎn)刪除
下圖是RegionServer搶占任務(wù)以及搶占任務(wù)之后的工作流程:
假設(shè)Master當(dāng)前發(fā)布了4個(gè)任務(wù)啤握,即當(dāng)前需要回放4個(gè)日志文件,分別為hlog1局扶、hlog2恨统、hlog3和hlog4
RegionServer1搶占到了hlog1和hlog2日志,RegionServer2搶占到了hlog3日志三妈,RegionServer3搶占到了hlog4日志
以RegionServer1為例畜埋,其搶占到hlog1和hlog2日志之后會(huì)分別分發(fā)給兩個(gè)HLogSplitter線程進(jìn)行處理,HLogSplitter負(fù)責(zé)對(duì)日志文件執(zhí)行具體的切分畴蒲,切分思路還是首先讀出日志中每一個(gè)<HLogKey, WALEdit>數(shù)據(jù)對(duì)悠鞍,根據(jù)HLogKey所屬Region寫入不同的Region Buffer
每個(gè)Region Buffer都會(huì)有一個(gè)對(duì)應(yīng)的寫線程,將buffer中的日志數(shù)據(jù)寫入hdfs中,寫入路徑為/hbase/table/region2/seqenceid.temp咖祭,其中seqenceid是一個(gè)日志中某個(gè)region對(duì)應(yīng)的最大sequenceid
針對(duì)某一region回放日志只需要將該region對(duì)應(yīng)的所有文件按照sequenceid由小到大依次進(jìn)行回放即可
這種Distributed Log Splitting方式可以很大程度上加快整個(gè)故障恢復(fù)的進(jìn)程掩宜,正常故障恢復(fù)時(shí)間可以降低到分鐘級(jí)別。然而么翰,這種方式會(huì)產(chǎn)生很多日志小文件牺汤,產(chǎn)生的文件數(shù)將會(huì)是M * N,其中M是待切分的總hlog數(shù)量浩嫌,N是一個(gè)宕機(jī)RegionServer上的Region個(gè)數(shù)檐迟。假如一個(gè)RegionServer上有200個(gè)Region,并且有90個(gè)hlog日志码耐,一旦該RegionServer宕機(jī)追迟,那這種方式的恢復(fù)過程將會(huì)創(chuàng)建 90 * 200 = 18000個(gè)小文件。這還只是一個(gè)RegionServer宕機(jī)的情況骚腥,如果是整個(gè)集群宕機(jī)小文件將會(huì)更多!@椤剿骨!
3. Distributed Log Replay
該方案在基本流程上做了一些改動(dòng)浓利,如下圖所示:
相比Distributed Log Splitting方案贷掖,流程上的改動(dòng)主要有兩點(diǎn):先重新分配Region苹威,再切分回放HLog牙甫;Region重新分配打開之后狀態(tài)設(shè)置為recovering窟哺,核心在于recovering狀態(tài)的Region可以對(duì)外提供寫服務(wù)且轨,不能提供讀服務(wù)泳挥,而且不能執(zhí)行split屉符、merge等操作筑煮。
DLR的HLog切分回放基本框架類似于Distributed Log Splitting粤蝎,但它在分解完HLog為Region-Buffer之后并沒有去寫入小文件初澎,而是直接去執(zhí)行回放碑宴。這樣設(shè)計(jì)可以大大減少小文件的讀寫IO消耗延柠,解決DLS的切身痛點(diǎn)贞间。
可見雹仿,在寫可用率以及恢復(fù)性能上胧辽,DLR要遠(yuǎn)遠(yuǎn)優(yōu)于DLS方案摄咆,官方也給出了一個(gè)簡(jiǎn)單的測(cè)試報(bào)告:
可見吭从,DLR在寫可用恢復(fù)是最快的影锈,讀可用恢復(fù)稍微弱一點(diǎn),但都比DLS好很多
了解了DLR的解決方案后枣抱,再回頭看DLS的實(shí)現(xiàn)方案佳晶,就不禁要問上一句:為什么DLS需要將Buffer中的數(shù)據(jù)寫入小文件中轿秧?個(gè)人認(rèn)為原因最有可能是寫入小文件之后菇篡,同一個(gè)Region的不同日志數(shù)據(jù)可以按照文件名中sequenceid由小到大進(jìn)行順序回放驱还,完全可以模擬當(dāng)時(shí)數(shù)據(jù)寫入的流程议蟆,不會(huì)有絲毫偏差咐容。而如果不寫小文件戳粒,很難在分布式環(huán)境下對(duì)sequenceid進(jìn)行排序享郊,這里就有一個(gè)問題炊琉,不按順序?qū)Log進(jìn)行回放會(huì)不會(huì)出現(xiàn)問題苔咪?這個(gè)問題可以分為下面兩個(gè)層面進(jìn)行討論:
- 不同時(shí)間更新的相同rowkey柳骄,不按順序回放會(huì)不會(huì)有問題耐薯?比如WAL1中有<rowkey, t1>,WAL2中有<rowkey,t2>体谒,正常情況下應(yīng)該先回放<rowkey,t1>對(duì)應(yīng)的日志數(shù)據(jù)抒痒,再回放<rowkey,t2>對(duì)應(yīng)的日志數(shù)據(jù)故响,如果順序顛倒會(huì)不會(huì)有問題?
第一眼看到這樣的問題覺得一定不行啊伪冰,正常情況下<rowkey,t2>對(duì)應(yīng)的日志數(shù)據(jù)才是最后的真正數(shù)據(jù),一旦顛倒之后不就變成<rowkey,t1>對(duì)應(yīng)的日志數(shù)據(jù)了坯墨。
這里需要關(guān)注更新時(shí)間的概念捣染,rowkey回放時(shí),如果寫入時(shí)間戳定義為回放時(shí)間的話畔勤,肯定會(huì)有異常的庆揪。但是如果回放日志數(shù)據(jù)的時(shí)候rowkey寫入時(shí)間戳被定義為當(dāng)時(shí)rowkey數(shù)據(jù)寫入日志的時(shí)間的話吝羞,就正常了钧排。按照上面例子恨溜,順序即使顛倒糟袁,先寫<rowkey,t2>再寫<rowkey,t1>,但是寫入數(shù)據(jù)的時(shí)間戳(版本)依然保持不變時(shí)t2和t1的話五嫂,最大版本數(shù)據(jù)還是<rowkey,t2>沃缘,用戶讀取最新數(shù)據(jù)依然是<rowkey,t2>槐臀,和回放順序并沒有關(guān)系氓仲。
- 相同時(shí)間戳更新的相同rowkey晰洒,不按順序回放會(huì)不會(huì)有問題谍珊?比如WAL1中有<rowkey, t0>砌滞,WAL2中有<rowkey,t0>坏怪,正常情況下也應(yīng)該先回放前者贝润,再回放后者,如果順序顛倒會(huì)不會(huì)有問題铝宵?
為什么同一時(shí)間會(huì)有多條相同rowkey的寫入更新打掘,而且還在不同的日志文件中?大家肯定會(huì)有這樣的疑問捉超。問題中‘同一時(shí)間’的單位是ms胧卤,在很多寫入吞吐量很大的場(chǎng)景下同一毫秒寫入大量數(shù)據(jù)并不是不可能,那先后寫入兩條相同rowkey的數(shù)據(jù)也必然可能拼岳,至于為什么在不同文件枝誊,假如剛好第一次更新完rowkey的時(shí)候日志截?cái)嗔耍诙胃戮蜁?huì)落入下一個(gè)日志叶撒。
那這種情況下前后兩次更新時(shí)間戳還一致,顛倒順序就辦法分出哪個(gè)版本大了呀落君!莫慌,不是還有sequenceid~,只要在回放的時(shí)候?qū)⑷罩緮?shù)據(jù)原生sequenceid也一同寫入,不就可以在時(shí)間戳相同的情況下根據(jù)sequenceid進(jìn)行判斷了。具體實(shí)現(xiàn)只需要寫入一個(gè)replay標(biāo)示(用來(lái)表示該數(shù)據(jù)時(shí)回放寫入)和相應(yīng)的sequenceid扒最,用戶在讀取的時(shí)候如果遇到兩個(gè)都帶有replay標(biāo)示而且rowkey强挫、cf八匠、column抡四、時(shí)間戳都相同的情況厌处,還需要比較sequenceid就可以分辨出來(lái)哪個(gè)數(shù)據(jù)版本更大瑰排。
具體可以參考這個(gè)官方j(luò)ira:https://issues.apache.org/jira/browse/HBASE-8701
在0.95版本DLR功能已經(jīng)基本實(shí)現(xiàn),一度在0.99版本已經(jīng)設(shè)為默認(rèn),但是因?yàn)檫€是有一些功能性缺陷(主要是在rolling upgrades的場(chǎng)景下可能會(huì)導(dǎo)致數(shù)據(jù)丟失)户魏,又在1.1版本取消了默認(rèn)設(shè)置纵寝。用戶可以通過設(shè)置參數(shù)hbase.master.distributed.log.replay = true 來(lái)開啟DLR功能闹啦,當(dāng)然前提是將HFile格式設(shè)置為V3(v3格式HFile引入了tag功能江场,replay標(biāo)示就是用tag進(jìn)行實(shí)現(xiàn)的)
總結(jié)
本文主要介紹了HLog相關(guān)知識(shí),同時(shí)基于此對(duì)HBase中RegionServer宕機(jī)之后整個(gè)恢復(fù)流程以及原理進(jìn)行了深入分析权均,重點(diǎn)分析了DLS方案以及DLR方案必搞,希望和大家一起學(xué)習(xí)HBase故障恢復(fù)模塊知識(shí)庶诡。文中如有錯(cuò)誤晴玖,可以一起討論學(xué)習(xí)。