HBase 查詢導致RegionServer OOM故障復盤


 

背景:我司作為某運營商公司的技術咨詢公司,發現有第三方開發公司在使用HBase 1.1.2 (HDP 2.4.2.258版本)一段時間使用正常后,從某一天開始報OOM,從而導致RegionServer宕機。

故障排查步驟

  1. 查看 regionserver的log和stdout。由於是突然宕機,log沒有任何error信息,stdout 因為自動拉起以及默認啟動腳本是重定向覆蓋,所以被洗掉了;而oom dump當時還沒開啟,無任何明顯提示信息。
  2. regionserver的log中盡管沒有發現error信息,但發現了許多warning,BucketCache: Failed allocation for ${block_id}org.apache.hadoop.hbase.io.hfile.bucket.BucketAllocatorException: Allocation too big size=21342038。這雖然不是錯誤,但其實是個很有用的提示信息,說明可能存在着有許多大的block,無法寫入bucketcache讀緩存中。
  3. 嘗試重新拉起regionserver,但由於業務方疏忽,他們表示已停了所有程序,但卻依然沒完全停止讀取hbase程序,因此反復拉起regionserver失敗,此時可看到日志  java.lang.OutOfMemoryError: Requested array size exceeds VM limit
  4. 根據stacktrace進去讀源碼,發現是在做rpc fetch data的時候,ByteBufferOutputStream對象時用一個數組cache數據,bytes超過capacity上限后會把當前的capacity乘以2,new一個新的byte數組,把舊的數組內容copy到新的去。這種底層的類似c的寫法可以減少對象和隨機讀內存的開銷。但是源碼很蠢,分配的上限是Integer.MAX_VALUE,而眾所周知,Oracle/OpenJDK 7的數組只允許開到 Integer.MAX_VALUE - 2 ,因為用戶一個查詢過大,即使內存和網絡足夠好也會OOM導致RegionServer宕機,這明顯是個bug。[HBase 14978] [HBase 14946] 從issue看應該是在1.2.0以后加了對multi的限制,嘗試從服務前端避免這種問題發生,但本人尚未仔細閱讀1.2.0的源碼去確認是否真的修復。
  5. 由於業務方不知是對自己的數據不熟悉還是其他原因,一直不承認有大數據,於是我們通過反復實驗定位找回了查詢掛的語句,開了oom dump 獲取了宕機前的內存快照。通過對ByteBuffer對象的分析和反二進制化,發現了掛機時其內存吃到了1g,按照capacity翻倍,再翻倍就是2g超出了數組上限,完全符合錯誤棧信息。
  6. 從快照里獲取了一個看起來比較大的rowkey,get出來整個row有38m。而后我們又寫了個scan程序對全表scan並統計size,發現整體幾百k以上的數據也不少,還有少部分是10m以上的。在他們的20000/batch 的multi-get的場景,基本很容易掛。拿出數據與業務方對峙后,業務方承認數據可能是存在臟數據,他們之前實際遇到過類似問題。在寫入時報了 keyvalue size too large 的問題,但他們毫不在意,把配置的size改成了512m就寫入算了。

至此,故障已被成功排查。對於咨詢團隊來說,主要的任務已經完成了。

附:OOM錯誤完整 stacktrace
FATAL [IndexRpcServer.handler=5,queue=0,port=60020J regionserver.HRegionServer: Run out of memory; HRegionServer will abort itself immediately
java.lang.OutOfMemoryError: Requested array size exceeds VM limit 
    at  java.nio.HeapByteBuffer.<init>(HeapByteBuffer.java:57) 
    at  java.nio.ByteBuffer.allocate(ByteBuffer.java:331) 
    at org.apache.hadoop.hbase.io.ByteBufferOutputStream.checkSizeAnd6row(ByteBufferOutputStream.java:74) 
    at org.apache.hadoop.hbase.io.ByteBufferOutputStream.write(ByteBufferOutputStream.java:112) at org.apache.hadoop.hbase.KeyValue.oswrite{KeyVdlue.java:2881) 
    at org.apache.hadoop.hbase.codec.KeyValueCodec^KeyVdlueEncoder.writetKeyVdlueCodec.java:60) 
    at  org.apache.hadoop.hbase.ipc.IPCUtil.buildCeilBlock(IPCUti1.java:120)    at org.apache.hadoop.hbase.ipc.RpcServer$Call.setResponse(RpcServer.java:384)
    at org.apache.hadoop.hbase.ipc.CallRunner.run(CallRunner.java:128)
    at org.apache.hadoop.hbase.ipc.RpcExecutor.consumerLoop(RpcExecutor.java:112)
    at org.apache.hadoop.hbase.ipc.RpcExecutor$l.run(RpcExecutor.java:92)
    at java.lang.Th read.run(Th read.java:745)

相關原理簡要分析

bucketcache

參考:HBase BlockCache系列 – 走進BlockCache HBase BlockCache系列 - 探求BlockCache實現機制

BlockCache是Region Server級別的,一個Region Server只有一個Block Cache,在Region Server啟動的時候完成Block Cache的初始化工作。讀數據時,會先訪問blockcache,blockcache沒數據則從hdfs讀取數據嘗試寫入讀緩存,寫失敗則會拋warning直接返回數據,否則從讀緩存中返回數據。bucketcache是hbase讀緩存blockcache的一種實現,聽說是由阿里貢獻的,其他的還有LRUBlockCache,SlabCache等。大致的發展可以梳理為 LRUBlockCache -> DoubleBlockCache(LRU + Slab) -> CombinedBlockCache(LRU+Bucket)。

bucketcache 可以配置四種模式:none禁用,heap堆內,off-heap堆外,file文件。一般推薦開啟,file主要是針對ssd場景,off-heap配置不好會出現另外的direct memory OOM問題,具體計算較復雜,參見Configuring Off-heap Memory (BucketCache) - HortonWorks 

bucketcache實際上和本次故障的直接關系不大,因為通過源碼可以發現IPCUtil獲取的outputstream只有堆上的ByteBufferOutputStream,只是其warning信息可以幫我們進一步佐證有異常過大數據的猜想。BucketCache的相關調用和實現邏輯可參見HFileReaderV2BucketCache兩個類。

Best Practice

避免此類問題,須注意如下HBase使用技巧:

  1. 負責入庫的需做好數據限制,謹慎修改 keyvalue max size 限制,臟數據或不重要的數據可適當裁剪或丟棄,實在較大的數據考慮存hdfs,hbase存路徑去指向文件。
  2. 讀取時需大致估算平均每行數據大小,並適當留出冗余的內存,來決定一個multi get的batch大小。不需要的列字段就盡量不要讀,避免oom也可以節省性能。
  3. column family和qualifier盡可能短而精確,因為每一個keyvalue都會存qualifier。
  4. 如無必要,表的字段盡量不太太多。
  5. 動態qualifier慎用,除非你對你自己的數據有足夠清楚的上限了解。
  6. (其他)索引表和數據表盡量分離,不然scan會帶來額外不必要的開銷。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM