1. 程式人生 > >HBase寫入優化

HBase寫入優化

前言

在HBASE持續寫入的時候遇到一種奇怪的情況,寫著寫著HBase就會出現一陣寫入速度為0的情況。在網上查了很多資料,終於找到一篇外文的資料,有詳盡的實驗和解決方案,在此做一下翻譯。原文連線 (需要翻牆)

翻譯

問題

我們的程式是通過mapreduce job執行hive query。但mapper程式執行的時候會出現失敗(不斷的重複失敗,最終導致job被kill),報錯如下
java.io.IOException: org.apache.hadoop.hbase.client.ScannerTimeoutException: 63882ms passed since the last invocation, timeout is currently set to 60000

我們做了一些研究發現,這個異常是scanner的next方法沒有在超時限制的時間內被呼叫導致的。我們用一個簡單的複製table操作重現了這個錯誤,又通過hive 執行select overwrite table A select * from table B重現了同樣的錯誤。在兩個案例裡mapper程式被指定基於TableInputFormat(就像我們的Hive job)scan一個split,mapper只是簡單的掃描並且把記錄寫入新table。由於寫入操作被暫停,所以scanner的next方法沒有被呼叫,並出發了超時異常。

日誌

第一站——日誌。regionserver的日誌裡記錄瞭如下的log
WARN org.apache.hadoop.hbase.regionserver.MemStoreFlusher: Region occurrence,\x17\xF1o\x9C,1340981109494.ecb85155563c6614e5448c7d700b909e. has too many store files; delaying flush up to 90000ms


INFO org.apache.hadoop.hbase.regionserver.HRegion: Blocking updates for ‘IPC Server handler 7 on 60020′ on region occurrence,\x17\xF1o\x9C,1340981109494.ecb85155563c6614e5448c7d700b909e.: memstore size 128.2m is >= than blocking 128.0m size

阻止重新整理一段時間,這會導致停止寫入後續記錄並阻止呼叫scanner.next()。經過更多的研究(並測試了將hbase.client.scanner.caching設為1在內的各種不同的值)我們認為這就是需要解決的問題,但是這個問題是怎麼發生的呢?

為什麼它阻止寫入

阻止寫入的原因是memstore已經達到了我稱之為memstore blocking limit的限制。這個限制的大小是由hbase.hregion.memstore.flush.size *hbase.hregion.memstore.block.multiplier計算出來的,預設情況下是64M*2。正常情況下memstore達到flush.size時會被寫入storefile,但是當memstore達到128M並且store file太多時memstore就不能再寫入到store file中了(看上面log的第一行)。“too many storefiles”是由hbase.hstore.blockingStoreFiles 引數控制的(預設是7)。當一個memstore flush的時候就會產生一個新的store file,hbase的minor和major compaction機制會負責將這些store file 合併生成更大的store file,以減少storefile的數目。預設情況下compaction會在store file數目大於hbase.hstore.compactionThreshold (預設值是3)並小於hbase.hstore.compaction.max(預設值是7)的情況下被觸發。另外當memstore在regionserver中合併時使用了太多的堆記憶體後,無論flush.size被設成什麼值,memstore都會flush。可接受的heap設定是由設定hbase.regionserver.global.memstore.lowerLimit(預設0.35) 和hbase.regionserver.global.memstore.upperLimit(預設0.4)。有一個執行緒專門負責有規律的flush並檢查這些限制。

  • 如果flush執行緒甦醒,並且memstore的數目大於最小限制,它會開始flush(從當前最大的memstore開始)知道memstore的數目低於最小限制。
  • 如果flush執行緒甦醒,並且memstore數目大於上限的限制,它會阻止更新並且開始flush,直到memstore的數目小於下線的限制。

幸運的是我們沒有看到由超過heap上限設定導致的block,只是遇到了前面描述的“memstore blocking limit”。但是目前為止我們只知道我們可以調整的不同引數。

停止block

我們的目標是停止block,並且不會導致regionserver超出記憶體設定,這樣我們的mapper才能不超時。最明顯的問題是我們有太多的storefile了,以至於不能及時的把它們進行combination。注意我們有6G的記憶體分配給hbase,不能在增加了。

我們從提高memstore flush的大小開始, 這會在每次flush的時候產生更少但是更大的storefiles:

  • 首先我們嘗試了128M的flush size,保持block multiplier的值為2。但是不能解決問題,他仍然產生太多的storefile,並導致同樣的block(略好於64M的時候)
  • 然後我們嘗試了256M的flush size,並將block mulitplier的值設為4。log和ganlia都顯示在storefile達到256M前flush就發生了(大約達到130M的時候)“due to global heap pressure”這表示storefile消耗了太多的heap記憶體。這以為這我們還是產生了太多的檔案並且仍然有相同的block問題,但是隨著“memstore blocking limit”被設成1GB後(256M*4),memstore block反生的頻率已經低很多了,儘管仍然導致mapper被kill。

我們現在雖然產生了更少的storefile,但是他們仍然堆積的太快了。從ganlia我們看到壓縮對聯和storefile counts 無限制的增長,這意味著我們經常達到block限制。下一步我們準備嘗試每次合併更多的檔案,因此我們增大到compaction.max的值到20,這回有了一些不同。

如何減少storefile的數目呢?如果我們有更少的store , 我們需要建立更上的檔案並且減少memstore使用的heap記憶體,下一步我們增加了region的大小。這需要增加hbase.hregion.max.filesize的大小,從256M提升到了1.5G,並且使用更少的pre split重建了我們的table。這導致我們的region減少了75%的region。

然後一切看起來都好了,“Blocking updates”的在單次執行的時候出現的很少了,但是它仍然會導致1-2個job會被kill。我們嘗試了提高memstore.lowerLimit和upperLimit 到0.45和0.5,但是沒有什麼效果。

現在怎麼辦

情況看起來有點糟糕。經過不斷的盯著ganlia的圖,我們不斷的回顧到一個沒有被解釋的由storefile最終導致kill job的點。

hbase_write_flush

大約在job跑到一半的時候,storefile的大小會停下,並且逐步的增長知道job死掉。注意這張圖顯示的是平均值,hbase會保留很少flush以等待storefile達到1G然後開始block,這是我們job死掉的原因。回到log。

從圖的開始階段我們看到線很平滑,memstore在正好達到或者是略微超過256M的時候寫入storefile,這意味著memstore有足夠的記憶體。從log裡我們看到flush進行的很好,但是有下面這種log。

INFO org.apache.hadoop.hbase.regionserver.MemStoreFlusher: Under global heap pressure: Region uat_occurrence,\x06\x0E\xAC\x0F,1341574060728.ab7fed6ea92842941f97cb9384ec3d4b. has too many store files, but is 625.1m vs best flushable region’s 278.2m. Choosing the bigger.

這並沒有“delaying flush”的log那麼糟糕。但是它顯示出我們的heap剛好能處理flush。然後在12:20左右,我們看到越來越多的下面的log

WARN org.apache.hadoop.hbase.regionserver.MemStoreFlusher: Region uat_occurrence,”\x98=\x1C,1341567129447.a3a6557c609ad7fc38815fdcedca6c26. has too many store files; delaying flush up to 90000ms

然後這些log結束在下面的log出現的時候

INFO org.apache.hadoop.hbase.regionserver.wal.HLog: Too many hlogs: logs=35, maxlogs=32; forcing flush of 1 regions(s): ab7fed6ea92842941f97cb9384ec3d4b
INFO org.apache.hadoop.hbase.regionserver.MemStoreFlusher: Flush thread woke up with memory above low water.

因此現在是heap的太小了導致memstore在一開始的時候就被強制flush了(storefile增加的比我們能合併的多)。然後就會出現由於太多的storefile延遲flush(memstore開始變大——圖裡面顯示的),然後(WAL)開始在log中抱怨產生了太多的log,這導致強制flush storefile(這樣WAL HLog才能被安全的丟棄——這又增加了stroefile的數目)。flush thread甦醒後,發現已經超出heap限制了,開始嘗試flush,這僅僅是加重了問題(增加了更多的storefile)。job失敗不會馬上發生但是卻沒有任何輸出,大約13:00的時候memstore開始達到“memstore blocking limit”最終mapper死掉。

我們應該做什麼?

在memstore flush 大小的分析過程中知道發生了什麼值得欣慰,但是這僅僅增強了我們已經知道的事實——問題的癥結在storefile太多。所以我們要做什麼呢?提升storefile的最大數目,這就是方法!每個storefile在store中都消耗資源(file handles, xceivers,儲存metadata的heap),至就是為什麼file store的數目預設被限制在7上。但是對我們這種寫的壓力來說,這個值太小了、在一般的操作中我們不會達到這種規模的寫入,有足夠的能力將一組storefile一次性寫入,間歇性的爆發相對安全,因為我們知道晚上執行的major compaction會將多出的store file清理乾淨(從而釋放掉所有額外的資源)。我們將這個最大值設為了200,最終程式執行正常了!

總結

我們的問題是我們的compaction不能處理所有生成的storefile。我們嘗試修改了各種hbase引數去解決這個問題,但是最後最後解決這個問題的關鍵是將hbase.hstore.blockingStoreFiles 設成200,這個值可能2倍於我們真正需要的大小,但是可以留給一些不經常做的需要大量寫入的job一些緩衝空間。另外我們設定了更大的region,還有比預設更大的memstore大小。下面是我們修改hbase-site.xml 的一些片段。

<!-- default is 256MB 268435456, this is 1.5GB -->
  <property>
    <name>hbase.hregion.max.filesize</name>
    <value>1610612736</value>
  </property>

  <!-- default is 2 -->
  <property>
    <name>hbase.hregion.memstore.block.multiplier</name>
    <value>4</value>
  </property>

  <!-- default is 64MB 67108864 -->
  <property>
    <name>hbase.hregion.memstore.flush.size</name>
    <value>134217728</value>
  </property>

  <!-- default is 7, should be at least 2x compactionThreshold -->
  <property>
    <name>hbase.hstore.blockingStoreFiles</name>
    <value>200</value>
  </property>

最後,如果我們的compaction能夠更快或者更頻繁,我們可能就能跟上storefile的建立了。但是如果不採取多執行緒compaction的方法看起來就不太可能實現,這個需求已經在新版本的hbase中有所提及,因此如果你有這類問題,一個升級可能就能解決,這次確實提示我們去考慮升級到CDH4.