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

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

  作者:鲍凤其
  爱可生 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,而是使用到的时候才会进行分配。 总结
  到这里,整个堆外内存泄露的排查就结束了。希望对大家有帮助。

矿业投资并购实务特别篇四印度尼西亚的矿业权证制度车林睿矿法投资评估近年来,随着一带一路倡议的实施和中国矿业走出去步伐的加速,中国对东盟国家的矿业投资呈高速增长态势。印度尼西亚的煤炭石油镍锡铜金等矿产储量位居世界前列,是东盟国家中代表委员热议数字平台促高质量就业建议发挥平台招培一体独特优势中国经济周刊记者周琦就业是最大的民生,一头连着万家灯火,一头系着社会经济发展。今年政府工作报告中,城镇新增就业预期目标从去年的1100万人以上上调为1200万人左右。新的一年,面对20230310ETF实盘DAY51收益246。8(五连跌)头条创作挑战赛实盘介绍起始资金26。8K,每月10号的下一个交易日流入4K增加资金。短期目标2024年五一节,总资金到10W。实盘概况实盘持仓再来一个五连跌,本月开始浮亏了。上一个老百姓何时能买得起房??针对国内房地产,如何防止炒房,遏制房价过高,让普通百姓都能住上房?房地产是国民经济重要的支柱产业,对经济的发展有着巨大的贡献,但同时也带来了一系列的问题,其中最为突出的就是炒房和房独家消息称FITURE健身镜解散近百人北美团队,回应仍在运转界面新闻记者徐诗琪界面新闻编辑近日,界面新闻获悉国内健身镜创业公司FITURE关停了大部分北美业务,并裁撤了近百人的团队。一位北美销售团队员工在领英上发帖称随着FITURE关停北美硅谷银行倒闭缘于资产负债策略出错,美联储会不会提前结束加息周期?记者王玉美国硅谷银行成为美国2008年金融危机以来宣布倒闭的最大银行,也是美国历史上的第二大倒闭银行,仅次于2008年倒闭的华盛顿互惠银行(WashingtonMutualInc)倒计时1天!2023年怀化市3。15国际消费者权益日纪念大会于3月12日举行315国际消费者权益日即将来临,为凝聚社会各方力量,展示消费维权成效,营造放心消费环境,提振消费信心,切实维护广大消费者合法权益,推动依法维权理念深入人心,3月12日9001300牛!蒙自米线卖到北美啦近日,一批产自云南红河州蒙自市重1。43吨的过桥米线经昆明海关所属蒙自海关检验合格,通过海运出口至北美,标志着蒙自过桥米线实现首次出口。走进红河州蒙自南湖缘过桥米线有限公司的生产车新田黄永英带队调研项目建设工作红网时刻新闻3月13日讯(通讯员陈欣)3月10日,新田县委副书记县长黄永英带队调研项目建设工作,现场办公协调解决问题,全力推进下一步工作。县委常委副县长杨友忠等县领导参加。黄永英一2023三人篮球世界杯分组揭晓新华社日内瓦3月9日电(记者单磊林德韧)国际篮联9日官方公布了2023年三人篮球世界杯分组情况,中国女队被分在D组。今年5月30日至6月4日,三人篮球世界杯将在奥地利维也纳举行,男湖南发布179个互联网全民义务植树项目湖南日报3月11日讯(全媒体记者彭雅惠)义务植树进入线上线下融合发展新阶段。连日来,省林业局发布179个互联网全民义务植树项目,鼓励大众云端植树,扫码尽责。我省是国家互联网全民义务
这个铁路博物馆上新项目了,值得体验!最近有家铁路博物馆成为新晋网红打卡地这里有复古帅气的蒸汽机车年代感十足的铁路老物件高颜值的百年德式建筑还有三大热门项目跟小编一起来看看吧!高颜值复古风婚纱照取景地胶济铁路博物馆位于在不丹得到佛陀启示和授记与圣祖对话的奇妙体验(一)善缘这是跨越2500年的对话。相比于2500年的时间跨距,跨越喜马拉雅山的高度已变得微不足道了。我的祖母生活在河南山村,但信奉佛教,在我出生之时,为我起名蒙古国为何要推广回鹘式蒙古文?蒙古国是一个位于中国和俄罗斯之间的内陆国,总面积约156万平方公里,人口约330万。蒙古国的官方语言是蒙古语,文字有两种,一种是西里尔蒙古文,一种是回鹘式蒙古文。西里尔蒙古文也被称佩戴舒适,动静皆宜南卡RunnerPro4骨传导耳机开箱国产厂牌NANK南卡早前发布了自家的骨传导Runner系列,得益于不俗的性价比及优秀的产品品质,市场的成绩和反响相当不错,最近南卡推出了Runner的续力新款RunnerPro4,从云南的大理古城避暑到西双版纳过冬,短租旅居中的经验和体会每天早晨被催着起床集合,然后上车睡觉,下车尿尿,到了旅游景区被导游限定时间赶着打卡照相,然后被旅游大巴车全部带走去景区购物点购物。这种跟团旅游很多人应该很熟悉,虽然省了钱,但是累了6款经典的高颜值草花,皮实好养爱爆花,养起来真过瘾,适合新手6款经典的高颜值草花,皮实好养爱爆花,养起来真过瘾,适合新手草本花卉一直都是花友喜欢的花卉之一,虽然看上去比较普通,但开花之后也是很好看的,最主要的是养护简单,新手也可以养出好状态电科数字2022年前三季度净利润2。91亿元同比增长3。61中证智能财讯电科数字(600850)10月29日披露2022年第三季度报告。2022年前三季度,公司实现营业总收入69。03亿元,同比增长0。47归母净利润2。91亿元,同比增长3核讹诈!美国发布2022年核态势评估报告,又想耍什么花招?军武次位面作者大伊万根据环球时报报道,当地时间10月27日,美国发布2022年度国防战略报告,并同步发布导弹防御评估报告和核态势审议报告,这是美军首次同步发布这三大战略报告,并对美班级中座位的安排,看似公平,实则都是套路?当孩子进入到小学后,与同学和老师相处的时间,要远远超过父母,教室也成为了学习和活动主要的场所。说到教室,很多家长会提到孩子的座位,孩子的座位在哪个地方?直接决定了在老师心中的位置,多走路能降血糖,有依据吗?研究结果公布,或与你想的不一样走路应该算是非常简单的一项运动,男女老少都可学会,对于孩子来说这更应该是人生当中的第1个技能,因为从呱呱落地就要开始,慢慢学会走路。其实走路也并不像我们理解当中的那样,如同婴儿一般父母的话里藏着孩子的未来,你说孩子是什么,孩子就会成为什么少年说里一位女孩吐槽妈妈是毒舌,总是无处不在地否定和打击她。妈妈是毒舌女孩考试没考好,妈妈便说你怎么考成这个鬼样子?如果你再考成这样,你都不用去学校了。弟弟在写作业,妈妈也会来一句