【presto】presto集群频繁OOM问题排查

前言:

今天早上09:30-09:40时分,presto集群又出现了多个worker节点OOM然后服务挂掉的问题。集群此时非常的不稳定。
在这里插入图片描述
看来了下节点的日志,是发生了内存堆溢出

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid171589.hprof ...

那么先观察一下这段时间跑的任务。由于公司的presto集群是配置了内存不足的保护策略的query.low-memory-killer.policy=total-reservation,所以先看一下任务是否又被主动kill掉的吧。

看一眼kibana:

09:30 - 09: 50 的任务数量其实也没有很多。
在这里插入图片描述

那么为什么集群内存不足,但没有触发OOM killer?

任务什么时候会被kill掉?

一个任务什么时候会被kill掉?

1.sql 本身使用的资源超出配置

相关的配置如下:

  • query.max-memory
  • query.max-memory-per-node
  • query.max-total-memory-per-node

2.集群内存不足时,触发内存保护机制
query.low-memory-killer.policy

  • none
  • total-reservation-on-blocked-nodes
  • total-reservation

先来再来看一眼集群的配置吧。


集群配置:

目前集群固定节点有23个节点,然后10点-17点处于集群繁忙状态,会增加5个节点。

config.properties

综上,Presto合适的内存分配配置为如下,-Xmx40G,Worker数大于23台,下面是config.properties文件一些重要的配置。

# 单个Query在整个集群上允许的最大user memory
query.max-memory=192GB
# 单个Query在单个Worker上允许的最大user memory
query.max-memory-per-node=10GB
# 单个Query在单个Worker上允许的最大user memory + system memory
query.max-total-memory-per-node=16GB
# 禁止掉RESERVED_POOL时
experimental.reserved-pool-enabled=false
# 这个内存主要是第三方库的内存分配,无法被统计跟踪,默认值是XMX*0.3。
memory.heap-headroom-per-node=8GB
# 集群内存不足时保护策略
query.low-memory-killer.policy=total-reservation

jvm.properties

下面是另一个重要的配置文件jvm.properties内容:

-server
-Xmx40G
-XX:-UseBiasedLocking
-XX:+UseG1GC
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError
-XX:+UseGCOverheadLimit
-XX:OnOutOfMemoryError=kill -9 %p
-DHADOOP_USER_NAME=hive
-Duser.timezone=Asia/Shanghai
-XX:G1ReservePercent=15
-XX:InitiatingHeapOccupancyPercent=40
-XX:ConcGCThreads=8

集群的配置第一眼看着没有什么问题,悬念留到后面再说。

参考文章:http://armsword.com/2020/02/18/presto-memory-kill-policy/


Presto集群内存不足时保护机制

再来看下集群内存不足时,代码逻辑吧!

判断节点是否内存不足:

代码位置:presto-main/src/main/java/com/facebook/presto/memory/ClusterMemoryPool.java

代码如下:

if (poolInfo != null) {
	 nodes++;
	  if (poolInfo.getFreeBytes() + poolInfo.getReservedRevocableBytes() <= 0) {
	      blockedNodes++;
}

内存不足kill任务的代码:

代码位置:
presto-main.src/main/java/com/facebook/presto/memory/ClusterMemoryManager.java

详细如下:

for (QueryExecution query : runningQueries) {
            boolean resourceOvercommit = resourceOvercommit(query.getSession());
            long userMemoryReservation = query.getUserMemoryReservation().toBytes();
            long totalMemoryReservation = query.getTotalMemoryReservation().toBytes();

            if (resourceOvercommit && outOfMemory) {
                // If a query has requested resource overcommit, only kill it if the cluster has run out of memory
                DataSize memory = succinctBytes(getQueryMemoryReservation(query));
                query.fail(new PrestoException(CLUSTER_OUT_OF_MEMORY,
                        format("The cluster is out of memory and %s=true, so this query was killed. It was using %s of memory", RESOURCE_OVERCOMMIT, memory)));
                queryKilled = true;
            }

            if (!resourceOvercommit) {
                long userMemoryLimit = min(maxQueryMemory.toBytes(), getQueryMaxMemory(query.getSession()).toBytes());
                if (userMemoryReservation > userMemoryLimit) {
                    query.fail(exceededGlobalUserLimit(succinctBytes(userMemoryLimit)));
                    queryKilled = true;
                }

                long totalMemoryLimit = min(maxQueryTotalMemory.toBytes(), getQueryMaxTotalMemory(query.getSession()).toBytes());
                if (totalMemoryReservation > totalMemoryLimit) {
                    query.fail(exceededGlobalTotalLimit(succinctBytes(totalMemoryLimit)));
                    queryKilled = true;
                }
            }

 // if the cluster is out of memory and we didn't trigger the oom killer we log the state to make debugging easier
            if (outOfMemory) {
                log.debug("The cluster is out of memory and the OOM killer is not called (query killed: %s, kill on OOM delay passed: %s, last killed query gone: %s).",
                        queryKilled,
                        killOnOomDelayPassed,
                        lastKilledQueryGone);
            }

此时一句log.debug的输出引起了我的注意:

// if the cluster is out of memory and we didn't trigger the oom killer we log the state to make debugging easier
            if (outOfMemory) {
                log.debug("The cluster is out of memory and the OOM killer is not called (query killed: %s, kill on OOM delay passed: %s, last killed query gone: %s).",
                        queryKilled,
                        killOnOomDelayPassed,
                        lastKilledQueryGone);
            }

翻译翻译:

如果集群内存不足并且我们没有触发 oom 杀手,我们会记录状态以使调试更容易

😯哦?那么我只需要看一下OOM节点的日志有没有输出这句话,看看是因为什么在集群内存不足时没有触发OOM killer?

正好有一个节点 开启了DEBUG级别日志。赶紧来查询一下:

结果出乎意料,日志里并没有出现这段话,那到底是什么原因导致节点OOM,但是没有?

虽然出现了新的疑问,但是却能解决第一个问题,为什么节点OOM,但是并没有触发内存不足保护机制,并不是我们保护机制写错或者配置不正确。

而是当时并没有出现内存不足,或者说还没来得及触发内存保护机制时,导致 JVM已经被kill掉了。

举个例子:之前JVM运行状态是正常的,一个新的计算任务分配到了这个节点上,瞬间导致JVM内存使用超过上限,然后 JVM 被kill 掉。

看来第二个可能性高一些。

也就是说有别的原因导致被kill掉。

  • 操作系统触发的OOM
  • 结点是否有内存泄露情况。(可以排除,有一个节点10分钟内挂掉2次)

于是将目光投向了第一个原因,是操作系统触发的OOM。

查看操作系统日志
dmesg > dmesg.txt

然后搜索 presto,发现并不是操作系统层面kill掉的presto。


后面当在看了一眼配置时,发现了问题:

-XX:OnOutOfMemoryError=kill -9 %p

-XX:OnOutOfMemoryError

当发生内存溢出的时候,还可以让JVM调用任一个shell脚本。大多数时候,内存溢出并不会导致整个应用都Crash掉,但是最好还是把应用重启一下,因为一旦发生了内存溢出,可能会让应用处于一种不稳定的状态,一个不稳定的应用可能会提供错误的响应。使用举例:

-XX:OnOutOfMemoryError=kill -9 %p

当给JVM传递上述参数的时候,如果发生了内存溢出,JVM会调用kill -9 将jvm 杀掉。


总结:

  • XX:OnOutOfMemoryErrorJVM级别的
  • query.low-memory-killer.policy=total-reservation是代码级别

两种内存保护策略起了 “冲突”,当preosto集群发生内存不足时,presto还没来得及触发内存不足保护策略,JVM那边就已经被kill掉了。

XX:OnOutOfMemoryError这个配置本意是好的,防止内存溢出后,程序不稳定主动kill掉,反而给我们带来了困惑,看来以后排查问题需要更加认真。

还有一点需要注意:即使我们去掉了XX:OnOutOfMemoryError的配置,presto 依旧发生了内存溢出的现象,只不过发生内存溢出的节点并不会被kill掉,这样能保证运行在这个节点上面没有发生OOM的任务能够顺利运行完成,避免雪崩式的任务失败

但是也会引起一些别的问题。所以presto判断内存不足的条件有点苛刻,并且就算检测到内存不足,presto才会在5分钟后,才去将任务杀死,我们应该让他更早的触发内存保护策略。

即让:poolInfo.getFreeBytes() + poolInfo.getReservedRevocableBytes() 比如小于JVM内存的15% ~ 20%。来早的触发内存保护策略。后续的文章有具体的代码改动详情。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值