天天看點

HBase運維實踐-聊聊RIT的那點事

相信長時間運維HBase叢集的童鞋肯定都會對RIT(Region-In-Transition,很多參考資料誤解為Region-In-Transaction,需要注意)有一種咬牙切齒的痛恨感,一旦Region處于長時間的RIT就會有些不知所措,至少以前的我就是這樣過來的。正所謂“恐懼來源于未知”,不知所措意味着我們對RIT知之甚少,然而“凡事都有因果,萬事皆有源頭”,處于RIT狀态的Region隻是肉眼看到的一個結果,為什麼會處于RIT狀态才是問題探索的根本,也是解決問題的關鍵。本文就基于hbase 0.98.9版本對RIT的工作機制以及實作原理進行普及性的介紹,同時在此基礎上通過真實案例講解如何正确合理地處理處于RIT狀态的Region。一方面希望大家能夠更好的了解RIT機制,另一方面希望通過本文的學習之後可以不再’懼怕’RIT,正确認識處于RIT狀态的Region。

從字面意思來看,Region-In-Transition說的是Region變遷機制,實際上是指在一次特定操作行為中Region狀态的變遷,那這裡就涉及這麼幾個問題:Region存在多少種狀态?HBase有哪些操作會觸發Region狀态變遷?一次正常操作過程中Region狀态變遷的完整流程是怎麼樣的?如果Region狀态在變遷的過程中出現異常又會怎麼樣?

HBase在RegionState類中定義了Region的主要狀态,主要有如下:

HBase運維實踐-聊聊RIT的那點事

上圖中實際上定義了四種會觸發Region狀态變遷的操作以及操作對應的Region狀态。其中特定操作行為通常包括assign、unassign、split以及merge等,而很多其他操作都可以拆成unassign和assign,比如move操作實際上是先unassign再assign;

這個過程有點類似于狀态機,也是通過事件驅動的。和Region狀态一樣,HBase還定義了很多事件(具體見EventType類)。此處以unassign過程為例說明事件是如何驅動狀态變遷的,見下圖:

HBase運維實踐-聊聊RIT的那點事

上圖所示是Region在close時的狀态變遷圖,其中紅字部分就是發生的各種事件。可見,如果發生M_ZK_REGION_CLOSING事件,Region就會從OPEN狀态遷移到PENDING_CLOSE狀态,而發生RS_ZK_REGION_CLOSING事件,Region會從PENDING_CLOSE狀态遷移到CLOSING狀态,以此類推,發生RS_ZK_REGION_CLOSED事件,Region就會從CLOSING狀态遷移到CLOSED狀态。當然,除了這些事件之外,HBase還定義了很多其他事件,在此就不一一列舉。截至到此,我們知道Region是一個有限狀态機,那這個狀态機是如何正常工作的,HMaster、RegionServer、Zookeeper又在狀态機工作過程中扮演了什麼角色,那就接着往下看~

接下來本節以unassign操作為例對這個流程進行解析:

整個unassign操作是一個比較複雜的過程,涉及HMaster、RegionServer和Zookeeper三個元件:

1. HMaster負責維護Region在整個操作過程中的狀态變化,起到一個樞紐的作用。它有兩個重要的HashMap資料結構,分别為regionStates和regionsInTransition,前者用來存儲整個叢集中所有Region及其當時狀态,而後者主要存儲在變遷過程中的Region及其狀态,後者是前者的一個子集,不包含OPEN狀态的Regions;

2. RegionServer負責接收HMaster的指令執行具體unassign操作,實際上就是關閉region操作;

3. Zookeeper負責存儲操作過程中的事件,它有一個路徑為/hbase/region-in-transition的節點。一旦一個Region發生unssign操作,就會在這個節點下生成一個子節點,子節點的内容是一個“事件”經過序列化的字元串,并且Master會監聽在這個子節點上,一旦發生任何事件,Master就會監聽到并更新Region的狀态。

下圖是整個流程示意圖:

HBase運維實踐-聊聊RIT的那點事

1. HMaster先執行事件M_ZK_REGION_CLOSING并更新RegionStates,将該Region的狀态改為PENDING_CLOSE,并在regionsInTransition中插入一條記錄;

2. 發送一條RPC指令給擁有該Region的RegionServer,責令其關閉該Region;

3. RegionServer接收到HMaster發送過來的指令之後,首先生成一個RS_ZK_REGION_CLOSING事件,更新到Zookeeper,Master監聽到ZK節點變動之後更新regionStates,将該Region的狀态改為CLOSING;

4. RegionServer執行真正的Region關閉操作:如果該Region正在執行flush或者compaction,等待操作完成;否則将該Region下的所有Memstore強制flush;

5. 完成之後生成事件RS_ZK_REGION_CLOSED,更新到Zookeeper,Master監聽到ZK節點變動之後更新regionStates,将該Region的狀态改為CLOSED;

到這裡,基本上将unssign操作過程中涉及到的Region狀态變遷解釋清楚了,當然,其他諸如assign操作基本類似,在此不再贅述。這裡其實還有一個問題,即關于HMaster上所有Region狀态是否需要持久化的問題,剛開始接觸這個問題的時候想想并不需要,這些處于RIT的狀态資訊完全可以通過Zookeeper上/region-in-transition的子節點資訊建構出來。然而,在閱讀HBase Book的相關章節時,看到如下資訊:

于是就充滿了疑惑,一方面Master更新hbase:meta是一個遠端操作,代價相對很大;另一方面Region狀态記憶體更新和遠端更新保證一緻性比較困難;再者,Zookeeper上已經有相應RIT資訊,再持久化一份并沒有太大意義。為了對其進行确認,就查閱跟蹤了一下源碼,發現是否持久化取決于一個參數:hbase.assignment.usezk,預設情況下該參數為true,表示使用zk情況下并不會對Region狀态進行持久化(詳見RegionStateStore類),可見HBase Book的那段說明存在問題,在此特别說明~

再回顧unassign的整個過程就會發現一次完整操作涉及太多流程,任何異常都可能會導緻Region處于較長時間的RIT狀态,好在HBase針對常見的異常做了最基本的容錯處理:

1. Master當機重新開機:Master在當機之後會丢失所有記憶體中的資訊,也包括RIT資訊以及Region狀态資訊,是以在重新開機之後會第一時間重建這些資訊。重新開機之後會周遊Zookeeper上/hbase/regions-in-transition節點下的所有子節點,解析所有子節點對應的最後一個‘事件’,解析完成之後一方面借此重建全局的Region狀态,另一方面根據狀态機轉移圖對處于RIT狀态的Region進行處理。比如如果發現目前Region的狀态是PENDING_CLOSE,Master就會再次據此向RegionServer發送’關閉Region’的RPC指令。

2. 其他異常當機:HBase會在背景開啟一個線程定期檢查記憶體中處于RIT中的Region,一旦這些Region處于RIT狀态的時長超過一定的門檻值(由參數hbase.master.assignment.timeoutmonitor.timeout定義,預設600000ms)就會重新執行unassign或者assign操作。比如如果目前Region的狀态是PENDING_CLOSE,而且處于該狀态的時間超過了600000ms,Master就會重新執行unassign操作,向RegionServer再次發送’關閉Region’的RPC指令。

可見,HBase提供了基本的重試機制,保證在一些短暫異常的情況下能夠通過不斷重試拉起那些處于RIT狀态的Region,進而保證操作的完整性和狀态的一緻性。然而不幸的是,因為各種各樣的原因,很多Region還是會掉入長時間的RIT狀态,甚至是永久的RIT狀态,必須人為幹預才能解決,下面一節内容讓我們看看都有哪些常見的場景會導緻Region會處于永久RIT狀态,以及遇到這類問題應該如何解決。

通過RIT機制的了解,其實可以發現處于RIT狀态Region并不是什麼怪物,大部分處于RIT狀态的Region都是短暫的,即使在大多數短暫異常的情況下HBase也提供了重試機制保證Region能夠很快恢複正常。然而在一些特别極端的場景下還是會發生一些異常導緻部分Region掉入永久的RIT狀态,進而會引起表讀寫阻塞甚至整個叢集的讀寫阻塞。下面我們舉兩個相關的案例進行說明:

現象:線上一個叢集因為未知原因忽然就卡住了,讀寫完全進不來了;另外還有很多處于PENDING_CLOSE狀态的Region。

分析:叢集卡住常見原因無非兩個,一是Memstore總消耗記憶體大小超過了上限進而觸發RegionServer級别flush,此時系統會阻塞叢集執行長時間flush操作;二是storefile數量過多超過設定的上限門檻值(參見:hbase.hstore.blockingStoreFiles),此時系統會阻塞所有flush請求而執行compaction。

診斷:

(1)首先檢視了各個RegionServer上的Memstore使用大小,并沒有達到設定的upperLimit。

(2)再檢視了一下所有RegionServer的storefile數量,瞬間石化了,store數為250的RegionServer上storefile數量竟然達到了1.5w+,很多單個store的storefile都超過了設定門檻值100

(3)初步懷疑是因為storefile數量過多引起的,看到這麼多storefile的第一反應是手動執行major_compaction,然而所有的compact指令好像都沒有起任何作用

(4)無意中發現所有RegionServer的Compaction任務都是同一張表music_actions的,而且Compaction時間都基本持續了一兩天。到此基本可以确認是因為表music_actions的Compaction任務長時間阻塞,占用了所有的Compaction線程資源,導緻叢集中所有其他表都無法執行Compaction任務,最後導緻StoreFile大量堆積

(5)那為什麼會存在PENDING_CLOSE狀态的Region呢?經檢視,這些處于PENDING_CLOSE狀态的Region全部來自于表music_actions,進一步診斷确認是由于在執行graceful_stop過程中unassign時遇到Compaction長時間阻塞導緻RegionServer無法執行Region關閉(參考上文unassign過程),因而掉入了永久RIT

解決方案:

(1)這個問題中RIT和叢集卡住原因都在于music_actions這張表的Compaction阻塞,是以需要定位Compaction阻塞的具體原因。經過一段時間的定位初步懷疑是因為這張表的編碼導緻,anyway,具體原因不重要,因為一旦Compaction阻塞,好像是沒辦法通過正常指令解除這種阻塞的。臨時有用的辦法是增大叢集的Compaction線程,以期望有更多空閑線程可以處理叢集中其他Compaction任務,消化大量堆積的StoreFiles

(2)而永久性消滅這種Compaction阻塞隻能先将這張表資料遷移出來,然後将這張表暴力删除。暴力删除就是先将HDFS對應檔案删除,再将hbase:meta中該表對應的相關資料清除,最後重新開機整個叢集即可。這張表删除之後使用hbck檢查一緻性之後,叢集Compaction阻塞現象就消失了,叢集就完全恢複正常。

現象:線上叢集很多RegionServer短時間内頻頻當機,有幾個Region處于FAILED_OPEN狀态

分析診斷:

(1)檢視系統監控以及RegionServer日志,确認RegionServer頻繁當機是因為大量CLOSE_WAIT狀态的短連接配接導緻。監控顯示短時間内(4h)CLOSE_WAIT的數量從0增長到6w+。

(2)再檢視RegionServer日志檢視到如下日志:

日志顯示,Region ‘3b3ae24c65fc5094bc2acfebaa7a56de’打開失敗,是以狀态被設定為FAILED_OPEN,原因初步認為是FileNotFoundException導緻,找不到的檔案是Region ‘b7b3faab86527b88a92f2a248a54d3dc’ 下的一個檔案,這兩者之間有什麼聯系呢?

(3)使用hbck檢查了一把,得到如下錯誤資訊:

看到這裡就一下恍然大悟,從引用檔案可以看出來,Region ‘3b3ae24c65fc5094bc2acfebaa7a56de’是‘ b7b3faab86527b88a92f2a248a54d3dc’的子Region,熟悉Split過程的童鞋就會知道,父Region分裂成兩個子Region其實并沒有涉及到資料檔案的分裂,而是會在子Region的HDFS目錄下生成一個指向父Region目錄的引用檔案,直到子Region執行Compaction操作才會将父Region的檔案合并過來。

到這裡,就可以了解為什麼子Region會長時間處于FAILED_OPEN狀态:因為子Region引用了父Region的檔案,然而父Region的檔案因為未知原因丢失了,是以子Region在打開的時候因為找不到引用檔案因而會失敗。而這種異常并不能通過簡單的重試可以解決,是以會長時間掉入RIT狀态。

(4)現在基本可以通過RegionServer日志和hbck日志确定Region處于FAILED_OPEN的原因是因為子Region所引用的父Region的檔案丢失導緻。那為什麼會出現CLOSE_WAIT數量暴漲的問題呢?經确認是因為Region在打開的時候會讀取Region對應HDFS相關檔案,但因為引用檔案丢失是以讀取失敗,讀取失敗之後系統會不斷重試,每次重試都會同datanode建立短連接配接,這些短連接配接因為hbase的bug一直得不到合理處理就會引起CLOSEE_WAIT數量暴漲。

解決方案:删掉HDFS上所有檢查出來的引用檔案即可

(5)有朋友咨詢為什麼會出現父檔案丢失,在此補充一下。目前有可能有幾個官方bug觸發這個問題,詳見https://issues.apache.org/jira/browse/HBASE-13331以及https://issues.apache.org/jira/browse/HBASE-16527。

簡單來說下在split完成之後父region會在什麼情況下被删除,實際上Master會啟動一個線程定時檢查完成split之後的父目錄是否可以被有效删除,系統meta表中會記錄該父region切分後的子region,隻需要檢查此兩個子region是否還存在引用檔案,如果都不存在引用檔案就可以認為該父region對應的檔案可以被删除。

HBASE-13331 bug是說檢查引用檔案時候(檢查是否存在、檢查是否可以正常打開)如果抛出IOException異常,函數就會傳回沒有引用檔案,導緻父region被删掉。正常情況下應該保險起見傳回存在引用檔案,保留父region,并列印日志手工介入檢視。

經過上面兩個案例的講解其實看出得出這麼幾點:

1. 永久性掉入RIT狀态其實出現的機率并不高,都是在一些極端情況下才會出現。絕大部分RIT狀态都是暫時的。

2. 一旦掉入永久性RIT狀态,說明一定有根本性的問題原因,隻有定位出這些問題才能徹底解決問題

3. 如果Region長時間處于PENDING_CLOSE或者CLOSING狀态,一般是因為RegionServer在關閉Region的時候遇到了長時間Compaction任務或Flush任務,是以如果Region在做類似于Major_Compact的操作時盡量不要執行unassign操作,比如move操作、disable操作等;而如果Region長時間處于FAILED_OPEN狀态,一般是因為HDFS檔案出現異常所緻,可以通過RegionServer日志以及hbck定位出來

RIT在很多運維HBase的人看來是一個很神秘的東西,這是因為RIT很少出現,而一旦出現就很緻命,運維起來往往不知所措。本文就希望能夠打破這種神秘感,還原它的真實本性。文章第一部分通過層層遞進的方式介紹了Region-In-Transition機制,第二部分通過生産環境的真實案例分析永久性RIT出現的場景以及應對的方案。希望大家能夠更多的了解RIT,通過不斷的運維實踐最後再也不用懼怕它~~

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

<a href="http://hbasefly.com/2016/09/08/hbase-rit/" target="_blank">原文連結</a>