一次 ES-APM 导致的概率性大量线程阻塞问题排查( 二 )

可以看到 CountedCompleter 类经过多次转换 , 从几 k 大小变为了 100k 以上 , 继续转换还变得更大 , 代码变得更复杂 , ASM 注入字节码的时间会变得越来越长 , 表现出来的现象就是:

  • 持有锁的线程花在转换类的时间会越来越长
  • 相应的 , 等待锁的线程将要等待更久的时间
我在代码中加了一些打印 , time 后面的两个数字 , 第一个是 ensureInstrumented 整个方法的耗时 , 第二个是 Bytebuddy 字节码注入的耗时 , 单位都是毫秒 。
[06:15:32][http-nio-18880-exec-49]done:class java.util.stream.Nodes$CollectorTask time:313306[06:15:33][http-nio-18880-exec-45]done:class java.util.stream.Nodes$ToArrayTask$OfRef time:496233...[06:15:50][http-nio-18880-exec-17]done:class java.util.stream.Nodes$ToArrayTask$OfRef time:183171075...[06:16:54][http-nio-18880-exec-27]done:class java.util.stream.Nodes$ToArrayTask$OfRef time:817135481...[06:22:51][http-nio-18880-exec-59]done:class java.util.stream.Nodes$CollectorTask time:43873324975...可以看到等锁的线程 , 有的等了 400 多秒 , bytebuddy 字节码注入的时间也在快速增大 , 后面甚至要几分钟 。
验证为了侧面印证前面的分析过程 , 可以验证这样的情况:第一次只有一个请求 , 没有用户造成的并发情况 , 让并发相关的类都快速注入完成 , 再来做压测 , 看看还会不会出问题 。 步骤如下:
  • 第一步 , 等服务启动完 , 使用 curl 请求 , 先请求一次接口 。
  • 然后使用 ab 或者 jmeter 疯狂来压那个接口 , 看看接口的响应情况 。
结果如预料中的一样 , 接口响应时间正常了 , jstack 查看现场的堆栈 , 也没有任何线程阻塞等在 ElasticApmAgent 的类锁上了 。
解决办法最简单的解决是业务暂时去掉 parallelStream , 有坑先绕过 。 还有一个不成熟的改法 , 也不知道对不对:把 synchronized 同步块里的鸡肋二次判断 contains 前面加一行获取最新 appliedInstrumentations 的逻辑 , 如下所示 , 如果已经转换过了 , 就可以跳过后面的逻辑了 。
一次 ES-APM 导致的概率性大量线程阻塞问题排查文章插图
经过重新打包 ES-APM 进行测试 , 确实解决了这个场景下的问题 。
小结【一次 ES-APM 导致的概率性大量线程阻塞问题排查】出问题的时候 , 不要怀疑自己 , 大胆的怀疑框架吧 , ES-APM 这个调用超级频繁的代码中有一个类锁性能好不到哪里去 , 有很大的改善空间 。 好了 , 落班 。
作者:挖坑的张师傅
链接: