首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

HBase Thrift2 CPU 100% 故障深度剖析:Scanner 泄漏導致 GC 風暴與三層治理方案

     編輯:Java 綜合教學

HBase Thrift2 CPU 100% 故障深度剖析:Scanner 泄漏導致 GC 風暴與三層治理方案

線上 HBase Thrift2 服務出現**單線程 CPU 長期 99%+、請求超時率飆升**。經 top -H、jstack、jstat、jmap 多工具鏈定位,根因為 **ClientScanner 未正確關閉導致對象堆積、老年代佔用 99.99%、CMS GC 持續併發失敗觸發 Full GC 風暴**。本文完整復盤現象觀測、線程定位、堆內存分析、代碼級根因、三層治理方案(客戶端修復、服務端補丁、API 替代)與預防體系。

一、 故障現象觀測

  • 外部連接 9090 端口大量超時,但 telnet 端口通
  • top -H 顯示 4 線程 CPU 99%+ (PID 20727-20730),呈波動狀態
  • 遷走流量後可訪問,但讀超時多於寫超時
  • 客戶端報錯:TransportException: EAGAIN (timedout)

二、 線程級定位:CPU 消費者是 GC 線程

# 1. 找出高 CPU 線程 ID (十進制)
ps -mp 20693 -o THREAD,tid,time | sort -rn
# 輸出示例:
# 20730 18.8%  (nid=0x50fa)
# 20729 18.7%  (nid=0x50f9)
# 20728 18.7%  (nid=0x50f8)
# 20727 18.7%  (nid=0x50f7)

# 2. 十六進制 nid 對應 jstack
# 0x50f7 = 20727, 0x50f8 = 20728 ...
"Gang worker#0 (Parallel CMS Threads)" os_prio=0 tid=0x... nid=0x50f7 runnable
"Gang worker#1 (Parallel CMS Threads)" os_prio=0 tid=0x... nid=0x50f8 runnable
# 確認:高 CPU 線程均為 CMS 並發 GC 線程

三、 GC 與堆狀態:老年代 99.99% 滿,Full GC 頻繁無效

# jmap -histo 20693 | head -30
   num  #instances  #bytes  class name
   1:    7283521  22518411456  [B                    (byte[] 數據)
   2:    4982714  1993085880  java.util.TreeMap$Entry
   3:    1285599  3617087664  java.util.TreeMap
   4:    4285217  445662568  org.apache.hadoop.hbase.client.ClientScanner
   5:    4285222  377099536  org.apache.hadoop.hbase.client.Scan
   6:    4284875  37069000  org.apache.hadoop.hbase.client.ScannerCallable
   7:    4285528  342921344  [Ljava.util.HashMap$Node;
   8:    4284880  308511360  org.apache.hadoop.hbase.client.ScannerCallableWithReplicas
   ...
   11:    4285283  205693584  org.apache.hadoop.hbase.client.RpcRetryingCaller

# 關鍵發現:
# ClientScanner 實例 428 萬+,佔用 ~400MB
# Scan、ScannerCallable、ScannerCallableWithReplicas 數量一致
# 推斷:大量 Scanner 未關閉,持有底層 RPC 連接、緩衝區、結果集

五、 代碼級根因:Thrift2 服務端 Scanner 管理缺陷

核心類:org.apache.hadoop.hbase.thrift2.ThriftServerRunner$HBaseHandler

// 簡化版關鍵邏輯
private final Map scannerMap = new ConcurrentHashMap<>();
private final AtomicInteger nextScannerId = new AtomicInteger(0);

@Override
public int openScanner(ByteBuffer table, TScan scan) throws TIOError, TException {
    Table htable = getTable(table);
    ResultScanner resultScanner = null;
    try {
        resultScanner = htable.getScanner(scanFromThrift(scan));
    } catch (IOException e) {
        throw getTIOError(e);
    } finally {
        closeTable(htable); // 僅關閉 Table,未關閉 Scanner!
    }
    // 將 Scanner 放入 Map,賴以追蹤
    return addScanner(resultScanner); // 返回 scannerId
}

private int addScanner(ResultScanner scanner) {
    int id = nextScannerId.getAndIncrement();
    scannerMap.put(id, scanner); // 關鍵:強引用持有,GC 無法回收
    return id;
}

@Override
public void closeScanner(int scannerId) throws TIOError, TIllegalArgument, TException {
    ResultScanner scanner = scannerMap.get(scannerId);
    if (scanner == null) throw new TIllegalArgument("Invalid scannerId");
    scanner.close();           // 真正釋放資源
    scannerMap.remove(scannerId); // 移除引用
}

泄漏鏈路

  1. 客戶端調用 openScanner → 服務端創建 ResultScanner → 放入 scannerMap
  2. 客戶端**未調用 closeScanner**(異常退出、網絡中斷、代碼邏輯漏調)
  3. scannerMap 永久強引用 ResultScanner → 關聯 Scan、ClientScanner、RpcRetryingCaller、byte[] 緩衝區全部無法 GC
  4. 老年代持續增長 → CMS 並發回收跟不上 → 觸發 Full GC → STW → CPU 100% → 請求超時

六、 三層治理方案

層級一:客戶端修復(源頭阻斷)

HBASE-17182

層級三:API 替代(架構規避)

改用 getScannerResults (一次性拉取所有結果) 替代 openScanner/getNext/closeScanner 流式 API,避免長連接 Scanner 狀態管理:

#!/bin/bash
# save-as: restart-thrift2-on-gc-pressure.sh
# 監控 Old Gen 使用率 > 90% 且 Full GC 頻率 > 1/min,自動重啟 Thrift2
PID=$(jps -l | grep ThriftServer | awk '{print $1}')
if [ -z "$PID" ]; then exit 0; fi

O_UTIL=$(jstat -gcutil $PID 1 1 | tail -1 | awk '{print $4}')  # O%
FGC_RATE=$(jstat -gc $PID 60000 2 | tail -1 | awk '{print $9}') # FGC/min

if (( $(echo "$O_UTIL > 90" | bc -l) )) && (( $(echo "$FGC_RATE > 1" | bc -l) )); then
    echo "$(date) GC pressure detected: O=${O_UTIL}%, FGC/min=${FGC_RATE}, restarting..."
    kill -TERM $PID
    sleep 10
    # 假設有 supervisord/systemd 管理自動拉起
fi

八、 預防體系建設

維度措施驗證方式
代碼規範強制 Scanner 使用 try-with-resources,Checkstyle 規則檢查CI 門禁
配置治理啟用 HBASE-17182 補丁,配置 TTL=1h、MaxScanners=10000啟動檢查腳本
監控告警Thrift2 進程 Old Gen% > 80%、FGC/min > 0.5、ScannerMapSize > 5000 告警Prometheus + Grafana
混沌演練定期殺死客戶端進程,驗證服務端 Scanner 自動回收演練報告
版本治理升級 HBase 2.4+ (內建 Scanner TTL)、或回港至 1.4 穩定分支升級驗收

HBase Thrift2 Scanner 泄漏是典型的「資源生命周期管理缺失」導致的 GC 風暴。通過「客戶端規範關閉、服務端 TTL 托底、架構層面 API 替代」三層防線,配合監控告警與混沌演練,可將此類故障從「深夜驚魂」轉化為「可控風險」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved