城市直播房产教育博客汽车
快传网
汽车报价
买车新车
博客专栏
专题精品
教育留学
高考读书
房产家居
彩票视频
直播黑猫
投资微博
城市上海
政务旅游

分布式令人头疼的堆外内存泄露怎么排查?

9月25日 乱人心投稿
  作者:鲍凤其
  爱可生dble团队开发成员,主要负责dble需求开发,故障排查和社区问题解答。少说废话,放码过来。
  本文来源:原创投稿
  爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
  大家在使用JavaNIO的过程中,是不是也遇到过堆外内存泄露的问题?是不是也苦恼过如何排查?
  下面就给大家介绍一个在dble中排查堆外内存泄露的案例。现象
  有客户在使用dble之后,有一天dble对后端MySQL实例的心跳检测全部超时,导致业务中断,最后通过重启解决。分析过程dble日志
  首先当然是分析dble日志。从dble日志中可以发现:故障时间点所有后端MySQL实例心跳都超时日志中出现大量Youmayneedtoturnuppagesize。ThemaximumsizeoftheDirectByteBufferPoolthatcanbeallocatedatonetimeis2097152,andthesizethatyouwouldliketoallocateis4194304的日志
  日志片段:心跳超时2022081511:40:32。147WARN〔TimerScheduler0〕(com。actiontech。dble。backend。heartbeat。MySQLHeartbeat。setTimeout(MySQLHeartbeat。java:251))heartbeatto〔xxxx:3306〕setTimeout,previousstatusis1堆外内存可能泄露的可疑ahrefhttps:www。bs178。comrizhitargetblankclassinfotextkey日志a2022081511:40:32。153WARN〔NIOREACTORBACKEND20RW〕(com。actiontech。dble。buffer。DirectByteBufferPool。allocate(DirectByteBufferPool。java:76))Youmayneedtoturnuppagesize。ThemaximumsizeoftheDirectByteBufferPoolthatcanbeallocatedatonetimeis2097152,andthesizethatyouwouldliketoallocateis4194304
  通过上面的日志猜测:所有MySQL实例超时是很特殊的,可能是由于故障时间发生了长时间停顿的gc而长时间停顿的gc可能是由于堆外内存不够,大量的业务流量涌进堆内存中,从而导致频繁的gc验证猜想
  为了验证上面的猜想,获取了dble机器的相关监控来看。
  故障时dble机器的内存图:
  可以看到确实存在短时间攀升。而dblecpu当时的使用率也很高。
  再来看dble中freebuffer的监控图(这个指标是记录dble中Processor的堆外内存使用情况的):
  从图中可以看到,从dble启动后堆外内存呈现递减的趋势。
  通过上面的监控图,基本可以确认故障发生时的时序关系:
  堆外内存长期呈现递减的趋势,堆外内存耗尽之后,在dble中会使用堆内存存储网络报文。
  当业务流量比较大时,堆内存被迅速消耗,从而导致频繁的fullgc。这样dble来不及处理MySQL实例心跳的返回报文,就引发了生产上的一些列问题。堆外内存泄露分析
  从上面的分析来看,根因是堆外内存泄露,因此需要排查dble中堆外内存泄露的点。
  考虑到dble中分配和释放堆外内存的操作比较集中,采用了btrace对分配和释放的方法进行了采集。btrace脚本
  该脚本主要记录分配和释放的对外内存的内存地址。
  运行此脚本后,对程序的性能有1020的损耗,且日志量较大,由于堆外内存呈长期递减的趋势,因此只采集了2h的日志进行分析:packagecom。actiontech。dble。btrace。importcom。sun。btrace。BTraceUimportcom。sun。btrace。annotations。;importsun。nio。ch。DirectBimportjava。nio。ByteBBTrace(unsafetrue)publicclassBTraceDirectByteBuffer{privateBTraceDirectByteBuffer(){}OnMethod(clazzcom。actiontech。dble。buffer。DirectByteBufferPool,methodrecycle,locationLocation(Kind。RETURN))publicstaticvoidrecycle(ProbeClassNameStringpcn,ProbeMethodNameStringpmn,ByteBufferbuf){StringthreadNameBTraceUtils。currentThread()。getName();排除一些线程的干扰if(!threadName。contains(writeTo)){StringjsBTraceUtils。jstackStr(15);if(!js。contains(heartbeat)!js。contains(XAAnalysisHandler)){BTraceUtils。println(threadName);if(buf。isDirect()){BTraceUtils。println(r:((DirectBuffer)buf)。address());}BTraceUtils。println(js);}}}OnMethod(clazzcom。actiontech。dble。buffer。DirectByteBufferPool,methodallocate,locationLocation(Kind。RETURN))publicstaticvoidallocate(ReturnByteBufferbuf){StringthreadNameBTraceUtils。currentThread()。getName();排除一些线程的干扰if(!threadName。contains(writeTo)){StringjsBTraceUtils。jstackStr(15);排除心跳等功能的干扰if(!js。contains(heartbeat)!js。contains(XAAnalysisHandler)){BTraceUtils。println(threadName);if(buf。isDirect()){BTraceUtils。println(a:((DirectBuffer)buf)。address());}BTraceUtils。println(js);}}}}分析采集的btrace日志
  采集命令:btraceoahrefhttps:www。bs178。comrizhitargetblankclassinfotextkey日志a的路径u11735pathtoBTraceDirectByteBuffer。java
  过滤出分配的堆外内存的地址:grepa:tmp14220dblebtrace。logallocat。txtseds。。allocat。txtallocataddr。txt删除前两个字符
  过滤出释放的堆外内存的地址:grepr:tmp14220dblebtrace。logrelease。txtseds。。release。txtreleaseaddr。txt删除前两个字符
  此时取两个文件的差集:sortallocataddr。txtreleaseaddr。txtuniqures。txt
  这样res。txt得到的是仅仅分配而没有释放的堆外内存(可能会有不准确)
  从中任选几个堆外内存的address,查看堆栈。排除掉误记录的堆栈后,出现最多的堆栈如下:complexQueryExecutor176019a:139999811142058com。actiontech。dble。buffer。DirectByteBufferPool。allocate(DirectByteBufferPool。java:82)com。actiontech。dble。net。connection。AbstractConnection。allocate(AbstractConnection。java:395)com。actiontech。dble。backend。mysql。nio。handler。query。impl。OutputHandler。init(OutputHandler。java:51)com。actiontech。dble。services。factorys。FinalHandlerFactory。createFinalHandler(FinalHandlerFactory。java:28)com。actiontech。dble。backend。mysql。nio。handler。builder。HandlerBuilder。build(HandlerBuilder。java:90)com。actiontech。dble。server。NonBlockingSession。executeMultiResultSet(NonBlockingSession。java:608)com。actiontech。dble。server。NonBlockingSession。lambdaexecuteMultiSelect55(NonBlockingSession。java:670)java。util。concurrent。ThreadPoolExecutor。runWorker(ThreadPoolExecutor。java:1149)java。util。concurrent。ThreadPoolExecutorWorker。run(ThreadPoolExecutor。java:624)java。lang。Thread。run(Thread。java:748)review代码
  在通过btrace知道了dble中的泄露点之后,下面就回到dble的代码中review代码。
  首先通过上面的堆栈定位到下面的代码:comactiontechdblebackendmysqlniohandlerbuilderHandlerBuilder。javapublicRouteResultsetNodebuild(booleanisHaveHintPlan2Inner)throwsException{TraceManager。TraceObjecttraceObjectTraceManager。serviceTrace(session。getShardingService(),buildexecutecomplexsql);try{finallongstartTimeSystem。nanoTime();BaseHandlerBuilderbuildergetBuilder(session,node,false);DMLResponseHandlerendHandlerbuilder。getEndHandler();泄露点在这,dble会创建OutputHandler实例,OutputHandler会分配堆外内存DMLResponseHandlerfhFinalHandlerFactory。createFinalHandler(session);endHandler。setNextHandler(fh);。。。RouteResultsetNoderouteSingleNodegetTryRouteSingleNode(builder,isHaveHintPlan2Inner);if(routeSingleNode!null)returnrouteSingleNHandlerBuilder。startHandler(fh);session。endComplexExecute();longendTimeSystem。nanoTime();LOGGER。debug(HandlerBuilder。buildcost:(endTimestartTime));session。setTraceBuilder(builder);}finally{TraceManager。finishSpan(session。getShardingService(),traceObject);}}comactiontechdblebackendmysqlniohandlerqueryimplOutputHandler。javapublicOutputHandler(longid,NonBlockingSessionsession){super(id,session);session。setOutputHandler(this);this。locknewReentrantLock();this。packetId(byte)session。getPacketId()。get();this。isBinarysession。isPrepared();分配堆外内存this。buffersession。getSource()。allocate();}
  通过上面的代码可以判断在构造复杂查询执行链的时候会分配堆外内存。
  问题到这其实还是没有解决,上述代码仅仅找到了堆外内存分配的地方,堆外内存没有释放仍然有以下几种可能:程序bug导致复杂查询未下发,从而执行链被丢弃而没有回收buffer程序下发了,由于未知bug导致没有释放buffer
  dble中复杂查询的下发和执行都是异步调用并且逻辑链比较复杂,因此很难通过review代码的方式确认是哪种情况导致。
  那如何进一步缩小范围呢?堆内存dump
  既然堆外内存泄露的比较快,平常状态下的dump文件中应该可以找到异常的没有被回收的OutputHandler实例。
  在dble复杂查询的执行链中,OutputHandler实例的生命周期往往伴随着BaseSelectHandler,因此是否可以通过异常OutputHandler的BaseSelectHandler来确定复杂查询有没有下发来进一步缩小范围。
  通过现场收集到的异常OutputHandler中buffer的状态是:
  正常写出的OutputHandler中buffer的状态是:
  找到的异常的OutputHandler的BaseSelectHandler中状态值:
  可以看出其中的状态值都是初始值,可以认为,异常的OutputHandler执行链没有被执行就被丢弃了。
  这样范围被进一步缩小,此次堆外内存泄露是由于程序bug导致复杂查询的执行链被丢弃而导致的。
  重新回到代码中,review下发复杂查询之前和构造之后的代码:comactiontechdblebackendmysqlniohandlerbuilderHandlerBuilder。javapublicRouteResultsetNodebuild(booleanisHaveHintPlan2Inner)throwsException{TraceManager。TraceObjecttraceObjectTraceManager。serviceTrace(session。getShardingService(),buildexecutecomplexsql);try{finallongstartTimeSystem。nanoTime();BaseHandlerBuilderbuildergetBuilder(session,node,false);DMLResponseHandlerendHandlerbuilder。getEndHandler();泄露点在这,dble会创建OutputHandler,OutputHandler会分配堆外内存DMLResponseHandlerfhFinalHandlerFactory。createFinalHandler(session);endHandler。setNextHandler(fh);。。。RouteResultsetNoderouteSingleNodegetTryRouteSingleNode(builder,isHaveHintPlan2Inner);if(routeSingleNode!null)returnrouteSingleN下发复杂查询,review之前的代码HandlerBuilder。startHandler(fh);session。endComplexExecute();longendTimeSystem。nanoTime();LOGGER。debug(HandlerBuilder。buildcost:(endTimestartTime));session。setTraceBuilder(builder);}finally{TraceManager。finishSpan(session。getShardingService(),traceObject);}}
  review到startHandler的时候,上一个语句returnrouteSingleNode引起了我的注意。
  按照逻辑,岂不是如果符合条件routeSingleNode!null,就不会执行startHandler,而直接返回了。而且执行链的作用域在本方法内,不存在方法外的回收操作,这不就满足了未下发而直接返回的条件了。
  至此,泄露的原因找到了。修复
  修复的话,在OutputHandler中,不采取预分配buffer,而是使用到的时候才会进行分配。总结
  到这里,整个堆外内存泄露的排查就结束了。希望对大家有帮助。
投诉 评论 转载

广州外卖下单后缺骑手配送?平台订单增多,将招募更多骑手12月15日,南都记者帮接到不少广州市民反映,称近日点外卖后一直无骑手接单,平台显示还在召唤骑手,导致送餐时间延长。记者注意到,不少网友也反映遇到类似情况。对此,外卖平台回复记……韩国足协取消了世界杯街头助威计划韩国足球协会(KFA)将取消世界杯(FIFAWorldCup)的助威计划。此前,首尔发生踩踏事件,已造成至少156人死亡。该组织表示,将撤回在11月20日至12月18日卡……成都往事七座暗藏玄机的桥,你知道几座?成都,一座因水而生,因水而兴的城市。在成都的河流之上,有无数特色各异的桥梁。而说到历史上最为著名的桥梁,首推七桥。由神鸟知讯成都市地志办联合推出的成都往事将为大家还原七桥的故事……王者近期英雄调整宫本猴子加强,李白调整是削弱还是加强?昨日体验服的英雄进行了调整,其中包括:1。花木兰削弱,将花木兰的伤害进行了下调。2。宫本加强,增加了一重势基础伤害与等级成长伤害,并增加了二技能的护盾值。3。……分布式令人头疼的堆外内存泄露怎么排查?作者:鲍凤其爱可生dble团队开发成员,主要负责dble需求开发,故障排查和社区问题解答。少说废话,放码过来。本文来源:原创投稿爱可生开源社区出品,原创内容未……大罗笑了!一家6口合影,30岁女友好继母,热恋7年,却未生娃巴西球星罗纳尔多有着幸福的大家庭,30岁的女友赛琳娜年轻漂亮,4个孩子也是非常听话,没有富二代的不良嗜好。近日,大罗的女友晒出了他们一家6口的合照,看起来非常的温馨、幸福,不得……你可能涉黄了?大数据扫黄或将启动,符合以下特征需留意进入21世纪后,各种科学技术与媒介手段的发展让人们叹为观止。上个世纪的人们,不能想象我们仅凭一部手机便能行走天下、了解所有信息的情况。互联网的发展让普通人受益良多,同时也……保健酒第一股不靠保健酒赚钱海南椰岛亏损何解?保健酒失利,海南椰岛发力新品类《中国科技投资》李晓娜罗晓凡11月10日至12日,海南椰岛(600238。SH)在第106届全国糖酒商品交易会(下称糖酒会)上发布了三……时代评选2022年最具影响力100人,TimCook再次入选《时代周刊》公布出2022年度全球最具影响力的100人。人物榜从2004年开始评选。百人榜共分为5大类,包括巨人(Titans)、先锋(Pioneers)、艺术家(Artist……说好再见,可再也未见那一年,他说了声再见,就离开了我!一、有人说,你做梦梦见的那个人,是因为那个人也在想你。我已经梦见过他几百次了,可她却再也没有想起过我。我在想,是不是梦做得还……CBA最新积分榜北京8883四川3连胜升第9北京时间2022年1月9日,2122赛季CBA常规赛第20轮继续酣战,在已经结束的4场比赛中,天津先行者88比106输辽宁本钢,江苏肯帝亚93比98负新疆伊力王酒,四川蓝鲸83……我以为别人尊重我,是因为我很优秀鲁迅先生说,我以为别人尊重我,是因为我很优秀。后来才明白,别人尊重我,是因为别人很优秀,优秀的人对谁都会很尊重。宽可以容人,厚可载德,厚德可以载物。乔布斯说:我特别……
阿德本罗拉伤休养55天后持续加重,大保健也能当医疗总监?018岁,这份养娃秘诀很靠谱!快来抄作业KS星光大赏,天赐已包揽2项冠军,2千万粉丝共同见证一哥的诞石景山里居然藏着一个宝藏网红博物馆午评赛道低迷,基建走强,接下来如何操作?无球可看?中国百万西甲球迷要哭了,这家上市公司拖欠版权费被索第一名的背后离不开晨读的习惯想娃成绩好,就要3读3不读轻松愉悦的樱桃节SMARTModular发布MP3000系列工业级PCIe42022年世界体坛收入榜孕期孕妈平躺睡觉时,胎儿难受给你发了信号,你发现了没?关于电脑配置,有这篇文章就够啦
她被埋在自家别墅外,男友成最大嫌疑人却被无罪释放女孩别怕张家口特产有哪些张家口有哪些特产热文聚热点网 一周亚马逊大事品牌注册重大变化,创建ASIN有效期属性变更大学生村官座右铭名媛拼团刷屏朋友圈,为何不来看看这些世界级钢琴女神?武王伐纣人民币在全球外汇储备中占比续刷新高导演齐柏林遇难乘坐飞机坠落原因是什么?赵薇又放飞?顶着一头乱糟糟的头发走机场,恕我欣赏不来!火锅老板的灾难科颜氏美白淡斑精华用完可以开灯吗金樱子泡酒里面的籽要不要搞掉

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找江西南阳嘉兴昆明铜陵滨州广东西昌常德梅州兰州阳江运城金华广西萍乡大理重庆诸暨泉州安庆南充武汉辽宁