Hadoop YARN RM内存泄露问题分析

一、问题描述

YARN 版本升至2.9.1以后,RM每过一段时间就会发生一次切换且GC时间巨长,通过监控发现RM的内存一直在缓慢的增加。

image

二、问题分析

通过分析RM内存发现,在RM内存中95.63%的空间被1603个RMNodeImpl占用。

image
image

在RMNodeImpl中有一个hashset中包含13w+个元素,导致每个RMNodeImpl约14M。

image

这包含14w+的hashset是completedContainers

image

为什么会有那么多的completedContainer没有释放呢?

当NM汇报心跳时,RMNodeImpl会把非Launched(即已完成)的和lost的container添加到completedContainers中。响应心跳时会将containersToBeRemovedFromNM中的container从completedContainers中删除,同时也会让NM删除containersToBeRemovedFromNM中的container。

//RMNodeImpl.java

publicvoidsetAndUpdateNodeHeartbeatResponse(

NodeHeartbeatResponseresponse) {

this.writeLock.lock();

try{

response.addAllContainersToCleanup(

newArrayList<ContainerId>(this.containersToClean));

response.addAllApplicationsToCleanup(this.finishedApplications);

//将containersToBeRemovedFromNM中的container发送给NM,让其删除

response.addContainersToBeRemovedFromNM(

newArrayList<ContainerId>(this.containersToBeRemovedFromNM));

response.addAllContainersToSignal(this.containersToSignal);

//从completedContainers中删除containersToBeRemovedFromNM中的container

this.completedContainers.removeAll(this.containersToBeRemovedFromNM);

this.containersToClean.clear();

this.finishedApplications.clear();

this.containersToSignal.clear();

this.containersToBeRemovedFromNM.clear();

//略

}

containersToBeRemovedFromNM中的container是从RMNodeFinishedContainersPulledByAMEvent事件中获取的。而RMNodeFinishedContainersPulledByAMEvent是由RMAppAttempt发送过来的。

//RMNodeImpl.java

publicstaticclassAddContainersToBeRemovedFromNMTransitionimplements

SingleArcTransition<RMNodeImpl,RMNodeEvent>{

​

@Override

publicvoidtransition(RMNodeImplrmNode,RMNodeEventevent) {

rmNode.containersToBeRemovedFromNM.addAll(((

RMNodeFinishedContainersPulledByAMEvent)event).getContainers());

  }

}

当RMAppAttempt接收到CONTAINER_FINISHED事件后,会去判断该container是不是AMContainer,如果不是AMContainer,则会将该container加到justFinishedContainers这个hashset中,AM汇报心跳时会调用RMAppAttempt.pullJustFinishedContainers()方法。RMAppAttempt.pullJustFinishedContainers()是清理completed containers的一个至关重要的方法。在该方法中首先会调用sendFinishedContainersToNM()方法将finishedContainersSentToAM中的container发送给NM,也就是在RMNodeI中从completedcontainers中删除的那部分container。然后将justFinishedContainers中的container转移到finishedContainersSentToAM中,这么做的目的是由AM决定这些container是否删除,而不是由NM决定。在下一次心跳的时候会再次将finishedContainersSentToAM中container发送给NM。

//RMAppAttempt.java

publicList<ContainerStatus>pullJustFinishedContainers() {

this.writeLock.lock();

​

try{

List<ContainerStatus>returnList=newArrayList<>();

​

// A new allocate means the AM received the previously sent

// finishedContainers. We can ack this to NM now

sendFinishedContainersToNM();//把Finished Container发送给AM

​

// Mark every containerStatus as being sent to AM though we may return

// only the ones that belong to the current attempt

booleankeepContainersAcrossAppAttempts=this.submissionContext

.getKeepContainersAcrossApplicationAttempts();

//将justFinishedContainers中的container转移到 finishedContainersSentToAM中,然后将

//justFinishedContainers清空

for(Map.Entry<NodeId,List<ContainerStatus>>entry:

justFinishedContainers.entrySet()) {

NodeIdnodeId=entry.getKey();

List<ContainerStatus>finishedContainers=entry.getValue();

if(finishedContainers.isEmpty()) {

continue;

     }

​

if(keepContainersAcrossAppAttempts) {

returnList.addAll(finishedContainers);

}else{

// Filter out containers from previous attempt

for(ContainerStatuscontainerStatus:finishedContainers) {

if(containerStatus.getContainerId().getApplicationAttemptId()

.equals(this.getAppAttemptId())) {

returnList.add(containerStatus);

         }

       }

     }

​

finishedContainersSentToAM.putIfAbsent(nodeId,

newArrayList<ContainerStatus>());

finishedContainersSentToAM.get(nodeId).addAll(finishedContainers);

   }

justFinishedContainers.clear();

​

returnreturnList;

}finally{

this.writeLock.unlock();

  }

}

在sendFinishedContainersToNM()方法中会从finishedContainersSentToAM中拿出nodeId和containerId用于构建RMNodeFinishedContainersPulledByAMEvent事件,然后清空finishedContainersSentToAM。

//RMAppAttempt.java

privatevoidsendFinishedContainersToNM() {

for(NodeIdnodeId:finishedContainersSentToAM.keySet()) {

​

// Clear and get current values

List<ContainerStatus>currentSentContainers=

finishedContainersSentToAM.put(nodeId,

newArrayList<ContainerStatus>());

List<ContainerId>containerIdList=

newArrayList<>(currentSentContainers.size());

for(ContainerStatuscontainerStatus:currentSentContainers) {

containerIdList.add(containerStatus.getContainerId());

   }

eventHandler.handle(newRMNodeFinishedContainersPulledByAMEvent(nodeId,

containerIdList));

  }

this.finishedContainersSentToAM.clear();

}

如果完成的是AMContainer,则会调用RMAppAttempt.amContainerFinished清理AM所占用的container。首先从RMAppAttemptContainerFinishedEvent事件中拿到NodeId和ContainerStatus加到finishedContainersSentToAM中,然后调用appAttempt.sendFinishedAMContainerToNM方法清理该container。

//RMAppAttempt.java

privatestaticvoidamContainerFinished(RMAppAttemptImplappAttempt,

RMAppAttemptContainerFinishedEventcontainerFinishedEvent) {

​

NodeIdnodeId=containerFinishedEvent.getNodeId();//获取NodeId

​

ContainerStatuscontainerStatus=

containerFinishedEvent.getContainerStatus();//获取ContainerStatus

if(containerStatus!=null) {

intexitStatus=containerStatus.getExitStatus();

if(shouldCountTowardsNodeBlacklisting(exitStatus)) {

appAttempt.addAMNodeToBlackList(nodeId);

   }

}else{

LOG.warn("No ContainerStatus in containerFinishedEvent");

  }

​

if(!appAttempt.getSubmissionContext()

.getKeepContainersAcrossApplicationAttempts()) {

appAttempt.finishedContainersSentToAM.putIfAbsent(nodeId,

newArrayList<ContainerStatus>());

//将container加入到finishedContainersSentToAM中

appAttempt.finishedContainersSentToAM.get(nodeId).add(containerStatus);

appAttempt.sendFinishedContainersToNM();//清理container

}else{

appAttempt.sendFinishedAMContainerToNM(nodeId,

containerStatus.getContainerId());

  }

}

基本流程如下(红线是与AM心跳有关):

考虑这一场景:一个普通container刚刚完成被加入到了justFinishedContainers中,这个container需要等待AM的心跳才可以被加入到finishedContainersSentToAM中,然后在AM的再一次心跳或者清理AMContainer时才可以被清理掉。如果在这个时候AM运行完成取消注册了,AM就不会再汇报心跳了,justFinishedContainers中的container就永远不会再有机会转移到finishedContainersSentToAM中,AMContainer在清理的时候也不会清理掉这部分的container。那么RMNodeImpl的completedContainers中的这些container也就不会被清理掉一直留在RM的内存中。

还有一个问题,如果RMAppAttempt已经处于最终状态(FINISHED,KILLED,FAILED),再接受到CONTAINER_FINISHED事件时,仅将该container加入到justFinishedContainer中,没有清理AMContainer。也会造成AMContainer释放不掉。

//RMAppAttempt.java

privatestaticfinalclassContainerFinishedAtFinalStateTransition

extendsBaseTransition{

@Override

publicvoid

transition(RMAppAttemptImplappAttempt,RMAppAttemptEventevent) {

RMAppAttemptContainerFinishedEventcontainerFinishedEvent=

(RMAppAttemptContainerFinishedEvent)event;

// Normal container. Add it in completed containers list

//仅仅将AMcontainer加入到justFinishedContainer中

addJustFinishedContainer(appAttempt,containerFinishedEvent);

  }

}

三、问题优化

问题产生的原因是普通container在被加入到justFinishedContainer中后,因为AM的取消注册来不及转移到finishedContainersSentToAM中,导致这些container被留在内存中。再加上处于最终状态的RMAppAttempt接收到CONTAINER_FINISHED事件后,没有对AMContainer进行清理,导致RMNodeImpl占用内存过大。

优化方案:处于最终状态的RMAppAttempt接收到CONTAINER_FINISHED事件后对AMContainer进行清理,并且在清理AMContainer的时候判断justFinishedContainer是否为空,如果不为空则将justFinishedContainer中的NodeId和ContainerId转移到finishedContainersSentToAM中发送给NM。

//RMAppAttempt.java

privatestaticfinalclassContainerFinishedAtFinalStateTransition

extendsBaseTransition{

@Override

publicvoid

transition(RMAppAttemptImplappAttempt,RMAppAttemptEventevent) {

RMAppAttemptContainerFinishedEventcontainerFinishedEvent=

(RMAppAttemptContainerFinishedEvent)event;

// Normal container. Add it in completed containers list

addJustFinishedContainer(appAttempt,containerFinishedEvent);

appAttempt.amContainerFinished(appAttempt,containerFinishedEvent);//清理AMContainer

  }

}

//RMAppAttempt.java

privatestaticvoidamContainerFinished(RMAppAttemptImplappAttempt,

RMAppAttemptContainerFinishedEventcontainerFinishedEvent) {

​

NodeIdnodeId=containerFinishedEvent.getNodeId();

​

ContainerStatuscontainerStatus=

containerFinishedEvent.getContainerStatus();

if(containerStatus!=null) {

intexitStatus=containerStatus.getExitStatus();

if(shouldCountTowardsNodeBlacklisting(exitStatus)) {

appAttempt.addAMNodeToBlackList(nodeId);

   }

}else{

LOG.warn("No ContainerStatus in containerFinishedEvent");

  }

​

if(!appAttempt.getSubmissionContext()

.getKeepContainersAcrossApplicationAttempts()) {

appAttempt.finishedContainersSentToAM.putIfAbsent(nodeId,

newArrayList<ContainerStatus>());

appAttempt.finishedContainersSentToAM.get(nodeId).add(containerStatus);

//将justFinishedContainer中的container转移到finishedContainersSentToAM中

if(!appAttempt.justFinishedContainers.isEmpty()) {

for(Map.Entry<NodeId,List<ContainerStatus>>justFinishedContainer

:appAttempt.justFinishedContainers.entrySet()) {

List<ContainerStatus>justFinishedContainerStatus=justFinishedContainer.getValue();

NodeIdjustFinishedNodeId=justFinishedContainer.getKey();

appAttempt.finishedContainersSentToAM.putIfAbsent(justFinishedNodeId,newArrayList<ContainerStatus>());

appAttempt.finishedContainersSentToAM.get(justFinishedNodeId).addAll(justFinishedContainerStatus);

     }

   }

appAttempt.sendFinishedContainersToNM();

}else{

appAttempt.sendFinishedAMContainerToNM(nodeId,

containerStatus.getContainerId());

  }

}

四、优化结果

---after completedContainers contains---打印的日志表示从completedContainers中删除containersToBeRemovedFromNM内的container后剩余的container。

优化前:NM多次心跳过后datanode1和datanode2的RMNodeImpl中依然有已完成任务的container。

优化后:completedContainers中没有container存在。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351