orc表导致hiveserver2内存暴涨问题分析

一、问题描述
昨天上午,钉钉上突然出现一堆hive相关的查询错误的报警。第一感觉,在yarn上查看任务日志,查询了一通,结果没看到有任务相关的报错。于是乎,立马查看hiveserver2的相关log,看到如下之类的信息:



大概的意思是由于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:


看似平平无奇的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到本地


# 注意一定要在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的底层去思考,所以,思考问题的方向很重要,不然就是浪费时间

原文链接:orc表导致hiveserver2内存暴涨问题分析_u013289115的博客-CSDN博客

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值