orc表導致hiveserver2內存暴漲問題分析

orc表導致hiveserver2內存暴漲問題分析

一、問題描述

昨天上午,釘釘上突然出現一堆hive相關的查詢錯誤的報警。第一感覺,在yarn上查看任務日誌,查詢了一通,結果沒看到有任務相關的報錯。於是乎,立馬查看hiveserver2的相關log,看到如下之類的信息:

Detected pause in JVM or host machine (eg GC): pause of approximately 15290ms
GC pool 'ConcurrentMarkSweep' had collection(s): count=1 time=15778ms

大概的意思是由於gc,導致hiveserver2整個服務停頓,stop the whole word!整整15秒不可用,對於軟件來說,是個毀滅性的災難!

爲什麼會突然飆升呢?

又多方面的查看hiveserver2的連接數監控

在這裏插入圖片描述

hive的連接數已經打滿了,log裏面也有thrift相關連接超時的問題,提示連接池已滿,hive.server2.thrift.max.worker.threads默認數是100,表示同時處理thrift連接的線程數,這個時候調到500,繼續觀察,這時候突然看了hiveserver2 的內存使用情況

在這裏插入圖片描述

驚人地發現竟然內存滿了!!調整上面那參數並沒有解決問題。當前hiveserver2的內存是4g,於是趕緊把內存調到8g,內存翻倍後,本以爲萬事大吉,調整後繼續觀察

在這裏插入圖片描述

不一會又跑滿了。是什麼鬼?hiveserver2又不做計算,爲嘛要喫那麼多內存?感覺像是無底洞,這時hiveserver2掛了,一堆數據開發提着大刀在路上,不停地傳來“hive查詢不了啦” 的聲音,慌的一批。以上單純調內存並沒有真正解決問題,下面進入解決過程。

二、解決過程

1.定位起因

經過不停的看hiveserver2的log,找尋一些內存暴漲前的sql,經過地毯式地搜索,定位到一條sql:

select * from  temp_xq_ann_user_profile_20200326 limit 5

看似平平無奇的sql,爲了復現,重啓hiveserver2後,內存迴歸正常,再次執行這條SQL,發現問題重新,內存再次暴漲,bingo,成功定位

2.分析sql

確定sql後,從這張表的特殊性開始下手,一查表結構

show create table temp_xq_ann_user_profile_20200326

在這裏插入圖片描述

竟然是個400多個字段的大寬表,而且是orc格式!注意,劃重點:orc,大寬表

3.深入分析

hiveserver2內存這麼高?裏面到底是什麼東西呢?於是,我把jvm dump到本地,在hiveserver2的節點執行拿到pid,再用jmap把整個jvm dump到本地

ps -ef|grep hiveserver2

在這裏插入圖片描述

# 注意一定要在hiveserver2所運行的用戶下執行,這裏是hive用戶
su hive
jmap -dump:format=b,file=hiveserver2.hprof 13767

再導出hiveserver2.hprof到windows桌面,用jdk自帶的jvisualvm.exe工具打開,這個工具在JAVA_HOME/bin下可以找到,導入後可以清晰的看到對象佔用的內存

在這裏插入圖片描述

我們可以看到 org.apache.orc.OrcProto$ColumnStatistics類的實例數最多,佔用堆內存最多。其次是byte和protobuf的類,這下有了初步的定位,確定了hiveserver2裏面佔用內存的是什麼東西了。

三、orc文件格式

這裏已經確定了orc相關的東西佔用內存太多,所以必須先了解下orc文件的結構

在這裏插入圖片描述

在ORC格式的hive表中,每個hdfs上的orc文件會被橫向的切分爲多個stripes,然後在每一個stripe內數據以列爲單位進行存儲,所有列的內容都保存在同一個文件中。每個stripe的默認大小爲64MB。ORC文件也以二進制方式存儲的,所以是不可以直接讀取,ORC文件也是自解析的,它包含許多的元數據,這些元數據都是通過ProtoBuffer進行序列化。除了藍色部分的主數據Raw Data之外,都可以稱作是metadata,這些都是通過pb序列化的。以下是metadata的protoc文件定義:

message Metadata {
  repeated StripeStatistics stripeStats = 1;
}

message StripeStatistics {
  repeated ColumnStatistics colStats = 1;
}

message ColumnStatistics {
  optional uint64 numberOfValues = 1;
  optional IntegerStatistics intStatistics = 2;
  optional DoubleStatistics doubleStatistics = 3;
  optional StringStatistics stringStatistics = 4;
  optional BucketStatistics bucketStatistics = 5;
  optional DecimalStatistics decimalStatistics = 6;
  optional DateStatistics dateStatistics = 7;
  optional BinaryStatistics binaryStatistics = 8;
  optional TimestampStatistics timestampStatistics = 9;
  optional bool hasNull = 10;
}

message IntegerStatistics  {
  optional sint64 minimum = 1;
  optional sint64 maximum = 2;
  optional sint64 sum = 3;
}

message DoubleStatistics {
  optional double minimum = 1;
  optional double maximum = 2;
  optional double sum = 3;
}

message StringStatistics {
  optional string minimum = 1;
  optional string maximum = 2;
  // sum will store the total length of all strings in a stripe
  optional sint64 sum = 3;
}

message BucketStatistics {
  repeated uint64 count = 1 [packed=true];
}

message DecimalStatistics {
  optional string minimum = 1;
  optional string maximum = 2;
  optional string sum = 3;
}

message DateStatistics {
  // min,max values saved as days since epoch
  optional sint32 minimum = 1;
  optional sint32 maximum = 2;
}

message TimestampStatistics {
  // min,max values saved as milliseconds since epoch
  optional sint64 minimum = 1;
  optional sint64 maximum = 2;
  optional sint64 minimumUtc = 3;
  optional sint64 maximumUtc = 4;
}

message BinaryStatistics {
  // sum will store the total binary blob length in a stripe
  optional sint64 sum = 1;
}

上面可以清晰地看到我們再jvm裏面分析的對象ColumnStatistics,同時也跟com.google.protobuf.LiteralByteString這個對象聯繫起來,二者也就是上圖jvm堆內存中佔用大量空間的罪魁禍首,裏面存了一個orc file中各個stripe中的各個column上的統計信息,如最大值、最小值等等,如下圖

在這裏插入圖片描述

因爲本身是400度個字段的大寬表,所以每個stripe存儲的column統計信息會越多,平常字段不多的表可能問題無法凸顯,現在我們現在可以確定的是,一個orc文件的stripe數量越多,需要存儲的統計信息越多,也就是ColumnStatistics對象實例會越多,佔用的內存空間會越大,即stripe數量與hiveserver2內存佔用呈正相關

四、問題驗證

已經猜測是單個orc文件的stripe數太多,我們來驗證一下,下面我從hive表對應的hdfs路徑拉取一個文件來統計stripe數,這個文件是440MB

hive --orcfiledump /user/lijf/data/test/000004_0 > statisc.txt

執行這條命令會把這個文件的所有統計信息存儲到本地文件,裏面的內容也就是ColumnStatistics對象要存儲的東西,包括stripe的編號,我們來看看這個文件總共多少個stripe

cat statisc.txt | grep 'Stripe[[:space:]]'

在這裏插入圖片描述

最大編號是1360,說明這個440MB的orc文件裏面竟然驚人的有1360個stripe!,平均每個stripe 320kb左右,遠低於默認的64MB!嚴重的不科學!這裏也驗證了我上面的猜測是正確的

五、解決方案

  • orc.stripe.size 的大小爲64MB或更多,客戶端嚴格限制此參數+服務端限制此參數+建表時指定此參數 總不會再錯。
  • set hive.exec.orc.split.strategy=BI; 設置這個參數會避免orc元數據緩存,默認參數本身是個優化,這裏取消掉
  • hive.fetch.task.conversion=none 取消hive默認的優化,強制並行化執行

六、總結分析

這裏是用的hive查詢,一個普通的select *查詢一個orc大寬表,由於沒有走計算,所以全部數據的加載讀取索引的壓力全在hiveserver2的身上,orc作爲列式存儲,它其中的一個的優勢是自帶的metadata數據,條件查詢的時候能夠加快索引,同時這也是它的一個致命傷,爲了快,必須要把這些元數據加載到內存中,如果是走計算的話比如select count(1),它能把計算和數據讀取分攤到各個節點,無壓力,偏偏是select * 的查詢不能分攤算力到各個節點,只能hiveserver2去扛住,一旦metadata的數據量超過hiveserver2承受範圍就gg了。這個問題最後的解決方案很簡單,但定位過程卻相當磨人。前半段時間主要花在普通運維的基礎上去思考問題,後面才從orc的底層去思考,所以,思考問題的方向很重要,不然就是浪費時間

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章