范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

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

  作者:鲍凤其
  爱可生 dble 团队开发成员,主要负责 dble 需求开发,故障排查和社区问题解答。少说废话,放码过来。
  本文来源:原创投稿
  *爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
  大家在使用 Java NIO 的过程中,是不是也遇到过堆外内存泄露的问题?是不是也苦恼过如何排查?
  下面就给大家介绍一个在dble中排查堆外内存泄露的案例。 现象
  有客户在使用dble之后,有一天dble对后端MySQL实例的心跳检测全部超时,导致业务中断,最后通过重启解决。 分析过程dble 日志
  首先当然是分析dble日志。从dble日志中可以发现: 故障时间点所有后端 MySQL 实例心跳都超时 日志中出现大量"You may need to turn up page size. The maximum size of the DirectByteBufferPool that can be allocated at one time is 2097152, and the size that you would like to allocate is 4194304"的日志
  日志片段: //心跳超时 2022-08-15 11:40:32.147  WARN [TimerScheduler-0] (com.actiontech.dble.backend.heartbeat.MySQLHeartbeat.setTimeout(MySQLHeartbeat.java:251)) - heartbeat to [xxxx:3306] setTimeout, previous status is 1      // 堆外内存可能泄露的可疑日志 2022-08-15 11:40:32.153  WARN [$_NIO_REACTOR_BACKEND-20-RW] (com.actiontech.dble.buffer.DirectByteBufferPool.allocate(DirectByteBufferPool.java:76)) - You may need to turn up page size. The maximum size of the DirectByteBufferPool that can be allocated at one time is 2097152, and the size that you would like to allocate is 4194304
  通过上面的日志猜测: 所有MySQL 实例超时是很特殊的,可能是由于故障时间发生了长时间停顿的gc 而长时间停顿的gc可能是由于堆外内存不够,大量的业务流量涌进堆内存中,从而导致频繁的gc 验证猜想
  为了验证上面的猜想,获取了dble机器的相关监控来看。
  故障时 dble 机器的内存图:
  可以看到确实存在短时间攀升。而 dble cpu 当时的使用率也很高。
  再来看 dble 中 free buffer的监控图(这个指标是记录dble中Processor的堆外内存使用情况的):
  从图中可以看到,从dble启动后堆外内存呈现递减的趋势。
  通过上面的监控图,基本可以确认故障发生时的时序关系:
  堆外内存长期呈现递减的趋势,堆外内存耗尽之后,在dble中会使用堆内存存储网络报文。
  当业务流量比较大时,堆内存被迅速消耗,从而导致频繁的fullgc。这样dble来不及处理MySQL实例心跳的返回报文,就引发了生产上的一些列问题。 堆外内存泄露分析
  从上面的分析来看,根因是堆外内存泄露,因此需要排查dble中堆外内存泄露的点。
  考虑到dble中分配和释放堆外内存的操作比较集中,采用了btrace 对分配和释放的方法进行了采集。 btrace 脚本
  该脚本主要记录分配和释放的对外内存的内存地址。
  运行此脚本后,对程序的性能有 10% - 20% 的损耗,且日志量较大,由于堆外内存呈长期递减的趋势,因此只采集了2h的日志进行分析: package com.actiontech.dble.btrace.script;     import com.sun.btrace.BTraceUtils; import com.sun.btrace.annotations.*; import sun.nio.ch.DirectBuffer;   import java.nio.ByteBuffer;   @BTrace(unsafe = true) public class BTraceDirectByteBuffer {       private BTraceDirectByteBuffer() {     }       @OnMethod(             clazz = "com.actiontech.dble.buffer.DirectByteBufferPool",             method = "recycle",             location = @Location(Kind.RETURN)     )     public static void recycle(@ProbeClassName String pcn, @ProbeMethodName String pmn, ByteBuffer buf) {         String threadName = BTraceUtils.currentThread().getName();         // 排除一些线程的干扰         if (!threadName.contains("writeTo")) {             String js = BTraceUtils.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(             clazz = "com.actiontech.dble.buffer.DirectByteBufferPool",             method = "allocate",             location = @Location(Kind.RETURN)     )     public static void allocate(@Return ByteBuffer buf) {         String threadName = BTraceUtils.currentThread().getName();         // 排除一些线程的干扰         if (!threadName.contains("writeTo")) {             String js = BTraceUtils.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日志
  采集命令: $ btrace -o 日志的路径 -u 11735 /path/to/BTraceDirectByteBuffer.java
  过滤出分配的堆外内存的地址: $ grep "^a:" /tmp/142-20-dble-btrace.log > allocat.txt $ sed "s/..//" allocat.txt > allocat_addr.txt # 删除前两个字符
  过滤出释放的堆外内存的地址: $ grep "^r:" /tmp/142-20-dble-btrace.log > release.txt $ sed "s/..//" release.txt > release_addr.txt # 删除前两个字符
  此时取两个文件的差集: $ sort allocat_addr.txt release_addr.txt | uniq -u > res.txt
  这样 res.txt 得到的是仅仅分配而没有释放的堆外内存(可能会有不准确)
  从中任选几个堆外内存的 address,查看堆栈。排除掉误记录的堆栈后,出现最多的堆栈如下: complexQueryExecutor176019 a:139999811142058 com.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.(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.lambda$executeMultiSelect$55(NonBlockingSession.java:670) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) java.lang.Thread.run(Thread.java:748) review 代码
  在通过btrace知道了dble中的泄露点之后,下面就回到dble的代码中 review 代码。
  首先通过上面的堆栈定位到下面的代码: // com/actiontech/dble/backend/mysql/nio/handler/builder/HandlerBuilder.java public RouteResultsetNode build(boolean isHaveHintPlan2Inner) throws Exception {     TraceManager.TraceObject traceObject = TraceManager.serviceTrace(session.getShardingService(), "build&execute-complex-sql");     try {         final long startTime = System.nanoTime();         BaseHandlerBuilder builder = getBuilder(session, node, false);         DMLResponseHandler endHandler = builder.getEndHandler();         // 泄露点在这,dble 会创建 OutputHandler实例,OutputHandler会分配堆外内存         DMLResponseHandler fh = FinalHandlerFactory.createFinalHandler(session);         endHandler.setNextHandler(fh);                   ...           RouteResultsetNode routeSingleNode = getTryRouteSingleNode(builder, isHaveHintPlan2Inner);         if (routeSingleNode != null)             return routeSingleNode;             HandlerBuilder.startHandler(fh);         session.endComplexExecute();         long endTime = System.nanoTime();         LOGGER.debug("HandlerBuilder.build cost:" + (endTime - startTime));         session.setTraceBuilder(builder);     } finally {         TraceManager.finishSpan(session.getShardingService(), traceObject);     }     return null; }       // com/actiontech/dble/backend/mysql/nio/handler/query/impl/OutputHandler.java public OutputHandler(long id, NonBlockingSession session) {     super(id, session);     session.setOutputHandler(this);     this.lock = new ReentrantLock();     this.packetId = (byte) session.getPacketId().get();     this.isBinary = session.isPrepared();     // 分配堆外内存     this.buffer = session.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 下发复杂查询之前和构造之后的代码: // com/actiontech/dble/backend/mysql/nio/handler/builder/HandlerBuilder.java public RouteResultsetNode build(boolean isHaveHintPlan2Inner) throws Exception {     TraceManager.TraceObject traceObject = TraceManager.serviceTrace(session.getShardingService(), "build&execute-complex-sql");     try {         final long startTime = System.nanoTime();         BaseHandlerBuilder builder = getBuilder(session, node, false);         DMLResponseHandler endHandler = builder.getEndHandler();         // 泄露点在这,dble 会创建 OutputHandler,OutputHandler会分配堆外内存         DMLResponseHandler fh = FinalHandlerFactory.createFinalHandler(session);         endHandler.setNextHandler(fh);                   ...           RouteResultsetNode routeSingleNode = getTryRouteSingleNode(builder, isHaveHintPlan2Inner);         if (routeSingleNode != null)             return routeSingleNode;           // 下发复杂查询,review 之前的代码         HandlerBuilder.startHandler(fh);         session.endComplexExecute();         long endTime = System.nanoTime();         LOGGER.debug("HandlerBuilder.build cost:" + (endTime - startTime));         session.setTraceBuilder(builder);     } finally {         TraceManager.finishSpan(session.getShardingService(), traceObject);     }     return null; }
  review 到 startHandler 的时候,上一个语句 return routeSingleNode 引起了我的注意。
  按照逻辑,岂不是如果符合条件 routeSingleNode != null ,就不会执行 startHandler,而直接返回了。而且执行链的作用域在本方法内,不存在方法外的回收操作,这不就满足了未下发而直接返回的条件了。
  至此,泄露的原因找到了。 修复
  修复的话,在OutputHandler中,不采取预分配buffer,而是使用到的时候才会进行分配。 总结
  到这里,整个堆外内存泄露的排查就结束了。希望对大家有帮助。

中年女人别穿得太花哨,今年流行简约风搭配,自然随意接地气就要这么穿并非所有女性都能拥有极为出众的颜值,在年龄增加的同时,她们的身材可能会走样,会变形。但这些外在的因素并不会影响女性对美的渴望,对美丽的向往。就算人到中年,也要用穿搭塑造好陈少霞的气质还是那么优雅,裙子的选择很端庄,有双儿范了穿裙子也是有风格讲究的,如果你选择的裙子又老土又沉闷的话,那你的裙子一定不够时尚。但如果你穿着的裙子特别可爱,特别时髦,和自己的气质不符合,那么你的时尚感也不会很强。所以,选择裙子官方李月汝已顺利抵美期待女篮双塔海外同框北京时间5月23日,中国篮球之队官方更新微博,透露中国女篮当家中锋李月汝以抵达美国,与WNBA芝加哥天空队会合,并送上了祝福。中国篮球之队官方写道恭喜小旭在WNBA的比赛中不负众望老美都慌了!中国,正加速开采海底黄金对于我国的能源问题来说,最关键的不是水能风能而是油气问题。近年来我国的非石化能用的发展势头强劲。能源石油在我国也有丰富的资源。渤海就是其中之一,这是继大庆油田之后发现最为重要的能源20岁演红楼梦第一美男子,走红后却自觉形象不佳,10年不拍戏2021年,伴随着一部剧的热播,人们的目光再次被一个熟悉的面容所吸引。满脸胡渣,头发花白,脸上的皱纹难掩岁月的沧桑。没错,他就是曾经饰演红楼第一美男子高宏亮。曾经的他风度翩翩,英俊珠宝,以爱为名(一)珠宝沉默不语,却比任何语言更能打动女人心。莎士比亚今天我们一起来看看,维多利亚时代珠宝走过的痕迹。维多利亚时代的珠宝如同维多利亚女王,奢华绚丽,让人目眩神迷。1837年年轻的维多利小金豆卖爆了,年轻人爱上养成系买黄金中国经济周刊记者孙冰北京报道集金豆攒金币是年轻人玩网络游戏时非常熟悉的环节,不过,现在越来越多的年轻人爱上了实景集金豆。重量1克的金豆在造型并没有什么特别,但却格外受到年轻人,尤其无精打采饮食减少常有困意却睡不好?可能是气虚!夏天来了,调理气虚的黄金季来了疲倦但睡眠差?小心气虚本报记者燕声如果你总是喜欢躺着懒得说话四肢无力饮食减少常有困意但睡眠不佳等表现,你可能是气虚了。从立夏开始,就正式进入了一年一度调理气虚的黄金季。这段时间把握大同猫咪博物馆全新升级,撸萌宠ampampamp遛娃约会,超多福利不要错过大同首家猫咪博物馆品牌升级啦!超大面积的萌宠博物馆,超多治愈系萌物,之前没去过的盆友们,趁着有这波福利,不要错过呀!探店攻略做好啦,认真看呀一进门即被浩瀚的星空顶吸引,整面墙的小黄哈佛研究孩子大脑发育黄金期很短,这几年家长尽量别错过孩子是否聪明到底谁的影响更大,先天遗传还是后天环境?很多人认为龙生龙,凤生凤,老鼠的儿子会打洞,孩子的智商主要得益于遗传,有些娃注定不可能成为学霸,真的是这样吗?今天就来说说这个问泰国羽毛球公开赛国羽一冠二亚,日本成为最大赢家最近有中国选手参加的国际大赛太少了,这样一来刚刚结束的汤尤杯和正在进行的泰国羽毛球公开赛就吸引了太多中国球迷的关注,现在比赛终于落下帷幕,中国选手闯进了三项决赛,最终以一冠两亚结束
中国男篮6月份集训备战世预赛杜锋主教练周琦为内线核心直播吧5月16日讯据沈阳日报记者马骋报道,中国男篮将于6月份开始集训备战世预赛。其中,杜锋仍然为男篮主教练,郭士强担任助理教练。男篮大名单目前还未公布,但一些主力球员已经确定。内线马卡姆巴佩已决定未来,上周球员与皇马完成签约直播吧5月16日讯马卡报消息,姆巴佩已决定未来,上周与皇马完成签约马卡指出,姆巴佩在上周已经与皇马完成了交易,下赛季他将正式成为皇马球员。他们封闭了近几个月讨论的所有东西,诸如巴黎中国广电即将放出新号段,或将改变三大运营商现状?本文原创,请勿抄袭和搬运,违者必究三大运营商一直是用户通信网络服务的主体,不论好坏,也没有别的选择了。以三大运营商一贯的风格,服务质量基本没有太大的差异。而对于这些情况,网上的评价尘埃落定!朱婷退役悬念揭晓,中国排协国际排联透露重要信息根据赛程安排,本年度世界女排联赛分站赛将会从5月31日开打,分为3站比赛,分别设在土耳其菲律宾和保加利亚。这也意味着,中国女排将会在1个月的时间内,连续前往3个国家参赛,对队员们的中国足协被匿名举报!相关部门已介入调查,金额高达18亿近段时间,关于中国足协有意退还调节费,帮助各俱乐部渡过财政危机的消息,引起外界关注。很多人并不清楚什么是调节费,听到高达18亿的金额时,第一反应是不是足协内部存在贪腐行为?其实真实次轮拿下?火箭有意拿下中国小将,斯通寻求交易,能取代小马丁吗北京时间5月16日,NBA季后赛的比赛依旧在如火如荼进行中,今天凯尔特人在东部半决赛的抢七大战中顺利淘汰了卫冕冠军雄鹿队,顺利晋级到了今年的东部决赛。其实在很多球迷关注季后赛的同时朗平奉献排球事业辉煌人生催人奋进为健康加酚,为新青年加油郎平,1960年12月10日出生于中国天津市武清区,前中国女子排球队著名运动员,世界冠军,奥运冠军,中国排球学院院长中国排球协会副主席,原中国女排原主教练。火箭蓄势待发,斯通亲自参加选秀抽签,1410新星基本锁定状元?NBA季后赛激战正酣,打进分区决赛的四支球队都已经确定。不过这一切跟休斯顿火箭没什么关系,管理层甚至普通球迷,都已经把目光放到接下来的选秀抽签中,这有可能会从一定程度上决定未来几年亚足联为何不让中国举办亚洲杯?理由太不公平,百亿投资打水漂近日,亚洲杯中国组委会官方正式宣布,经亚足联中国足协以及2023年亚洲杯中国组委会共同商议,决定取消原定于2023年6月16日至7月16日在中国举办的亚洲杯足球赛,与此同时,新的比瓜果飘香的城市新疆二家乡夏日的美景首先感谢头条给大家提供原创平台,可以让我们大家畅所欲言,也感谢小组长热心的宣传,衷心祝愿我们的平台能越来越好!我的家乡在祖国的最西北边新疆,新疆有着占全国面积六分之一中国最有特色的八大酒店,景观超赞,你想去住哪一个?在中国有许多富有特色的酒店,它们因为景观独特设备齐全,自带网红性质,成为游客们的网红打卡地1三亚亚特兰蒂斯酒店2上海深坑酒店3清远宝墩湖湖山温泉度假村4湖州喜来登温泉度假酒店5上海