专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

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

  作者:鲍凤其
  爱可生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。script;importcom。sun。btrace。BTraceUtils;importcom。sun。btrace。annotations。;importsun。nio。ch。DirectBuffer;importjava。nio。ByteBuffer;BTrace(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)returnrouteSingleNode;HandlerBuilder。startHandler(fh);session。endComplexExecute();longendTimeSystem。nanoTime();LOGGER。debug(HandlerBuilder。buildcost:(endTimestartTime));session。setTraceBuilder(builder);}finally{TraceManager。finishSpan(session。getShardingService(),traceObject);}returnnull;}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)returnrouteSingleNode;下发复杂查询,review之前的代码HandlerBuilder。startHandler(fh);session。endComplexExecute();longendTimeSystem。nanoTime();LOGGER。debug(HandlerBuilder。buildcost:(endTimestartTime));session。setTraceBuilder(builder);}finally{TraceManager。finishSpan(session。getShardingService(),traceObject);}returnnull;}
  review到startHandler的时候,上一个语句returnrouteSingleNode引起了我的注意。
  按照逻辑,岂不是如果符合条件routeSingleNode!null,就不会执行startHandler,而直接返回了。而且执行链的作用域在本方法内,不存在方法外的回收操作,这不就满足了未下发而直接返回的条件了。
  至此,泄露的原因找到了。修复
  修复的话,在OutputHandler中,不采取预分配buffer,而是使用到的时候才会进行分配。总结
  到这里,整个堆外内存泄露的排查就结束了。希望对大家有帮助。

(国际)世界经济论坛2023年年会将聚焦在分裂的世界中加强合作2023年世界经济论坛年会(达沃斯论坛)将于1月16日至20日在瑞士小镇达沃斯举办。本届年会主题为在分裂的世界中加强合作。1月15日,与会者在瑞士达沃斯会议中心拍照。新华社记者连漪女人到了中年,建议不穿大红大绿不扮嫩,打扮简约就很高级穿衣打扮虽然是改善个人形象的重要方式,但事实上效果却因人而异,对于不同年龄阶段的人有不一样的穿搭思路,这样才能展现优势。若是一味盲目追求时尚,反而会拉低气质。对于到了中年阶段的女人还原影院观感,我的家庭影音中心海信激光电视L5G视听体验电影感纵使时代变迁,但每部电影所承载的记忆都能随着影片被唤醒。住进电影院作为一个电影爱好者,在家也能获得电影院般的观影感受一直是我想要的生活,为此,之前我已经在家搭建了一套简单的家第30章您已进入珠穆朗玛峰国家级自然保护区!头条创作挑战赛晒出最美的她头条旅游在家云旅游在头条看见彼此我要上头条我要上微头条感谢头条我要上热门自驾云游季西藏川藏线自驾走吧自驾去旅行自驾好去处冬日美食季超凡体验,遇见美好遇见美字母哥缺阵霍勒迪3511,雄鹿逆转击败步行者字母哥因伤休战,但朱霍勒迪拿到35分11次助攻,率领雄鹿逆转击败步行者,送给对手四连败。霍勒迪全场19投13中,表现极其高效,顶替字母哥首发的鲍比波蒂斯贡献21分11个篮板4次助攻互联网时代来临之前,人类是怎么过日子的?一周新书推荐记者徐鲁青编辑黄月消失于互联网时代的100件事普鲁斯特曾在小说追忆似水年华中提起,每次闻到马德琳饼干泡茶的味道,就会回忆起童年往事,旧有物件触发记忆的方式比任何力量都要强大。纽约时油价暴涨,被逼得都买了混动!由于油价的暴涨,2022年被称为混动元年,很多自主品牌都推出了混动车型。混动车型比纯汽油车省油3050,在高油价的时代,的确很受欢迎,尤其是插电混动和增程式,还能充电,独立用电行驶烂尾楼业主稳了,央行出新政策保障了,银行提高提前还贷门槛针对今年的楼市,金融行业也是不断的出台各类扶持稳定发展的措施。金融监管部门也开会,出台了一些重磅政策,现在有不少利好消息。同时开年后,各地民众提前还房贷潮出现,但是奇怪的是,银行采2023年三大运营商竞争格局将发生哪些变化?这几年通信行业发展风格大变,过去围绕传统通信市场的白热化斗争明显放缓,过去围绕流量宽带携号转网等传统通信业务的低端竞争大幅减少,而围绕政企新业务的明争暗斗开始盛行,这里面又有两条主一日三省则知明无过在生活中,我每天大都是迷迷糊糊地忙忙碌碌,即使是有计划地行动,却不一定每次的行动都能得到期望中的结果。时间总是一晃而过,如果只懂得低下头不停地往前冲,那么就会渐渐地发现自己的计划无请不要一味地沉溺于悲伤,新年来临,要满怀希望!1。所谓希望什么是希望?鲁迅这样说希望本无所谓有的,无所谓无的,这正如地上的路,其实地上本没有路,走的人多了,也便成了路。这句话我一直记得,但有时候想想,还是觉得想不明白的事情,蛮
被商家认错了!周韦彤发文调侃红的是周雨彤,自己不红周雨彤名气虽然不大,但确确实实演了多部剧,并且还曾经和鹿晗龚俊井柏然这样的人气男星搭过戏,可惜只是配角,但是气质出众的她就算镜头很少,也可以让人一眼就记住了。而且周雨彤的穿搭也一直2天吸金近5000万!被禁赛的孙杨,这回找到新行业的财富密码了自从孙杨被判禁赛之后,他在近两年的生活包括他的工作,真的是相当煎熬。在去年年底的时候,孙杨曾经接受某网站给他颁发的奖项。因为当时孙杨已经沉寂一段时间,受尽冷落后却意外得到了外界的肯巩汉林郑重致歉区楚良等足球先生们巩老师关于收藏的有区楚良等先生签名的足球为赝品,未对签名认证,对区老师等人致意深切的歉意和不安,很是诚恳。坦承区先生等人都是巩老师心中的英雄,回忆他们参加足球赛,冲出亚洲,记录世界CBA最新排名辽宁广厦争第一,广东坐六望四,山东基本上岸北京时间3月17日CBA联赛第36轮的比赛正式拉开帷幕,最终天津山东吉林广州四川以及广厦都取得了比赛的胜利,因此CBA的积分榜发生了较大的变化。在比赛仅仅剩下两轮半的情况下,天津队中国短跑女将梁小静屡破纪录,速度惊呆黑人寒门出贵子,逆境出人才中国女飞人梁小静就是这句话最好的诠释。97年出生的她生活在一个贫困的家庭里,家庭的贫困使她营养不良,长期营养不良导致她身高不足一米六。但是她凭借一举之力奔向属CBA三消息广东名单终于正常,广厦想念钱德勒,王晗谈六人轮换大家好呀,我是北柠,各位小伙伴们要养成先赞后看的习惯哦!广东队从第二阶段常规赛开始就饱受球员伤病的困扰,可以说他们已经很久没有打过富余仗了,赵睿的缺席对于广东队后卫线的影响非常大,林志玲远嫁日本后形象逐渐日化,其形象对比以前堪比整容在现如今的女明星群体中,其中很多人的形象是要比她们的硬实力更为受到网友们的关注,像今天小优要分享的林志玲不就因为自远嫁日本后的形象变化,多次成为了很多人口中热议的对象。一点不夸张的离婚2个月后,Angelababy终于不装了前段时间,AB的庆生照大家都看了吗?没看过的这里集合,看过的也可以再重温一遍(谁不想看美女呢)只见AB穿着一身绿色抹胸翅膀纱裙徜徉在百花丛中,眉如远山黛,眼如秋波横,一头淡金色盘发ampampquot贪得无厌ampampquot邓伦代言费赶超杨幂,用前程赌钱程,补上亿窟窿1邓伦是怎样一步步被推进钱眼子里的?这屈辱的一幕或许足够解释邓伦早年拍过一部战争戏,剧组接受采访时,记者嫌他既没背景又不红,直接把话筒从他手上抢走递给了别的明星。看着别人面前挤满的小S曝大S具俊晔首次视频,大S直呼对方变好多,已在选择见面餐厅大S再婚已经进展到了快要见面的阶段,虽然具俊晔正在隔离,但一点儿也不妨碍徐家母女撒狗血抢话题,一个比一个戏多。大S跟妈妈虽然关系破冰,但是又曝出大S官宣当天,S妈气到冲去她家里,跟汪海林深夜发文引热议,疑内涵肖战,遭质疑后放肖战生图回怼近日,由肖战杨紫联袂主演的青春励志偶像剧余生,请多指教正在热播中,作为双顶流出演的热播剧,开播之后便取得非常亮眼的收视成绩。整体来讲,余生开播后的反响还挺不错,特别是两位主演的演技
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网