通常有兩種情況記錄檔需要進行replay:當叢集啟動時,或者當伺服器出錯時。當master啟動—(備份master轉正也包括在內)—它會檢查HBase在檔案系統上的根目錄下的.logs檔案是否還有一些檔案,目前沒有安排相應的region server。記錄檔名稱不僅包含了伺服器名稱,而且還包含了該伺服器對應的啟動碼。該數字在region server每次重啟後都會被重設,這樣master就能用它來驗證某個日誌是否已經被拋棄。
Log被拋棄的原因可能是伺服器出錯了,也可能是一個正常的叢集重啟。因為所有的region servers在重啟過程中,它們的log檔案內容都有可能未被持久化。除非使用者使用了graceful stop(參見the
section called “Node Decommission”)過程,此時伺服器才有機會在停止運行之前,將所有pending的修改操作flush出去。正常的停止指令碼,只是簡單的令伺服器失敗,然後在叢集重啟時再進行log的replay。如果不這樣的話,關閉一個叢集就可能需要非常長的時間,同時可能會因為memstore的並行flush引起一個非常大的IO高峰。
Master也會使用ZooKeeper來監控伺服器的狀況,當它檢測到一個伺服器失敗時,在將它上面的regions重新分配之前,它會立即啟動一個所屬它log檔案的恢複過程,這發生在ServerShutdowHandler類中。
在log中的修改操作可以被replay之前,需要把它們按照region分離出來。這個過程就是log splitting:讀取日誌然後按照每條記錄所屬的region分組。這些分好組的修改操作將會儲存在目標region附近的一個檔案中,用於後續的恢複。
Logs splitting的實現在幾乎每個HBase版本中都有些不同:早期版本通過master上的單個進程讀取檔案。後來對它進行了最佳化改成了多線程的。0.92.0版本中,最終引入了分布式log splitting的概念,將實際的工作從master轉移到了所有的region servers中。
考慮一個具有很多region servers和log檔案的大叢集,在以前master不得不自個串列地去恢複每個記錄檔—不僅IO超載而且記憶體使用量也會超載。這也意味著那些具有pending的修改操作的regions必須等到log split和恢複完成之後才能被開啟。
新的分布式模式使用ZooKeeper來將每個被拋棄的log檔案分配給一個region server。同時通過ZooKeeper來進行工作分配,如果master指出某個log可以被處理了,這些region servers為接受該任務就會進行競爭性選舉。最終一個region server會成功,然後開始通過單個線程(避免導致region server過載)讀取和split該log檔案。
註:可用通過設定hbase.master.distributed.log.splitting來關閉這種分布式log splitting方式。將它設為false,就是關閉,此時會退回到老的那種直接由master執行的方式。在非分布式模式下,writers是多線程的,線程數由hbase.regionserver.hlog.splitlog.writer.threads控制,預設設為3。如果要增加線程數,需要經過仔細的權衡考慮,因為效能很可能受限於單個log reader的效能限制。
Split過程會首先將修改操作寫入到HBase根資料夾下的splitlog目錄下。如下:
0 /hbase/.corrupt
0 /hbase/splitlog/foo.internal,60020,1309851880898_hdfs%3A%2F%2F \
localhost%2Fhbase%2F.logs%2Ffoo.internal%2C60020%2C1309850971208%2F \
foo.internal%252C60020%252C1309850971208.1309851641956/testtable/ \
d9ffc3a5cd016ae58e23d7a6cb937949/recovered.edits/0000000000000002352
為了與其他記錄檔的split輸出進行區分,該路徑已經包含了記錄檔名,因該過程可能是並發執行的。同時路徑也包含了table名稱,region名稱(hash值),以及recovered.edits目錄。最後,split檔案的名稱就是針對相應的region的第一個修改操作的序號。
.corrupt目錄包含那些無法被解析的記錄檔。它會受hbase.hlog.split.skip.errors屬性影響,如果設為true,意味著當無法從記錄檔中讀出任何修改操作時,會將該檔案移入.corrupt目錄。如果設為false,那麼此時會拋出一個IOExpectation,同時會停止整個的log splitting過程。
一旦log被成功的splitting後,那麼每個regions對應的檔案就會被移入實際的region目錄下。對於該region來說它的恢複工作現在才就緒。這也是為什麼splitting必須要攔截那些受影響的regions的開啟操作的原因,因為它必須要將那些pending的修改操作進行replay。
http://blog.cloudera.com/blog/2012/07/hbase-log-splitting/
http://duanple.blog.163.com/blog/static/709717672011923111743139/