一 背景

  在一次活动前的压测中,发现一个服务(平响为250ms左右)存在并发瓶颈,单实例的QPS压力从20升高到40后服务就雪崩了(平响急剧升高)。

  通过<jstack -F>命令查看线程信息,发现很多线程BLOCKED在打印日志的地方:

Thread 39120: (state = BLOCKED)
- java.lang.Throwable.printStackTrace(java.lang.Throwable$PrintStreamOrWriter) @bci=25, line=653 (Compiled frame)
- java.lang.Throwable.printStackTrace(java.io.PrintStream) @bci=9, line=643 (Compiled frame)
- java.lang.Throwable.printStackTrace() @bci=4, line=634 (Compiled frame)
- org.apache.logging.log4j.core.Logger.logMessage(java.lang.String, org.apache.logging.log4j.Level, org.apache.logging.log4j.Marker, org.apache.logging.log4j.message.Message, java.lang.Throwable) @bci=103, line=144 (Interpreted frame)
- org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(java.lang.String, org.apache.logging.log4j.Level, org.apache.logging.log4j.Marker, org.apache.logging.log4j.message.Message, java.lang.Throwable) @bci=8, line=2091 (Compiled frame)
- org.apache.logging.log4j.spi.AbstractLogger.logMessage(java.lang.String, org.apache.logging.log4j.Level, org.apache.logging.log4j.Marker, java.lang.String, java.lang.Object[]) @bci=186, line=1999 (Interpreted frame)
- org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(java.lang.String, org.apache.logging.log4j.Level, org.apache.logging.log4j.Marker, java.lang.String, java.lang.Object[]) @bci=21, line=1868 (Interpreted frame)
- org.apache.logging.slf4j.Log4jLogger.info(java.lang.String, java.lang.Object) @bci=20, line=183 (Compiled frame)

  该服务使用log4j-2.7打印日志,当时做了下面三个尝试:

  1. 从Logger改成asyncLogger,无效果;
  2. 减少日志量(只打印com.xxx.xxx包路径下的日志),单实例QPS压力升高到48后服务雪崩;
  3. 不打印info级别日志,单实例QPS压力到80服务依然正常;

  很疑惑,为什么日志打印对服务性能的影响如此大?而且单实例的QPS压力只有20也太小了(并发数只有5 = 20 / 1000ms / 250ms)!

二 排查

  分析<jstack -F>命令查出的线程信息,类Throwable的653行,printStackTrace()方法会对标准错误输出流(System.err)加同步锁(synchronized)。非常顺利,并发瓶颈的原因找到了!


但是,为什么logger.info会进入到Throwable.printStackTrace()呢?

错判1、jstack

  怀疑<jstack -F>命令查出的线程信息有问题,尝试用<jstack -l>命令,提示错误信息"well-known file is not secure",搜了下是由于<pid>进程的所有者与执行jstack命令的用户不一致,使用sudo未成功(机器权限问题,阻塞未解决)。

错判2、GC

  分析Throwable.printStackTrace()的上一行堆栈信息(类Logger的144行、类AbstractLogger的1992/1998行),怀疑是GC导致(历史经验,但讲不通),查看服务雪崩时的GC日志,发现确实GC频繁,搜了下CMS GC的相关文章,尝试修改JVM参数(内存大小、GC算法等),无效果。

错判3、log4j的bug

  Remote debug到测试环境上,在Throwable.printStackTrace()处断点,发现必现异常(ArrayIndexOutOfBoundsException:4)。于是使用关键字log4j+ArrayIndexOutOfBoundsException搜了下,找到log4j2的官方issue(https://issues.apache.org/jira/browse/LOG4J2-1542),不太对,继续浏览该关键字其他的bug issue,没有找到答案,想着要不提一个bug?但升级log4j的版本到2.13后无效果。

柳暗花明

  再次Remote debug到测试环境上,一步一步调试,发现会进入一些非本工程的代码且出现单词trace,想起来之前看到的通过字节码注入方式(jar包)打印trace日志的方案,怀疑是trace包内数组越界后catch异常同时e.printStackTrace()。最后找到trace包的提供者验证了该怀疑:

三 结论

通过字节码注入方式打印trace日志的jar包有一个数组越界的bug:

ThreadContext.put("XXX", ids[4]); // 数组ids大小为4

  此处会抛出ArrayIndexOutOfBoundsException,该异常被catch后调用了e.printStackTrace(),而Throwable.printStackTrace()方法会对标准错误输出流(System.err)加同步锁(synchronized),从而造成了服务的并发瓶颈。

printStackTrace()造成的并发瓶颈的更多相关文章

  1. .NET线程池最大线程数的限制-记一次IIS并发瓶颈

    .NET ThreadPool 最大线程数的限制 IIS并发瓶颈,有几个地方,IIS线程池的最大队列数,工作进程数,最大并发数.这些这里就不展开.主要是最近因为过度使用Task 导致的线程数占用过多, ...

  2. IIS并发瓶颈线程数的限制

    .NET线程池最大线程数的限制-记一次IIS并发瓶颈 https://www.cnblogs.com/7rhythm/p/9964543.html .NET ThreadPool 最大线程数的限制 I ...

  3. Redis为什么可以支持那么大的并发访问量?为什么redis没有单点并发瓶颈?

    一是redis使用内存 而是redis使用多路复用的IO模型: 现代的UNIX操作系统提供了select/poll/kqueue/epoll这样的系统调用,这些系统调用的功能是:你告知我一批套接字,当 ...

  4. JDK的多线程与并发库

    1.创建多线程 public class MultiThread { public static void main(String[] args) { // 通过继承Thread类 Thread th ...

  5. Vertica并发DML操作性能瓶颈的产生与优化(转)

    文章来源:中国联通网研院网优网管部IT技术研究团队 作者:陆昕 1. 引言 众所周知,MPP数据库以其分布式的超大存储能力以及列式的高速汇总能力,已经成为大数据分析比不可少的工具.Vertica就是这 ...

  6. 高并发的常见策略--大型web项目

    一个运营的系统在正式上线后将会遇到各种层级的高并发请求,因此我们必须对此做出相应的策略和技术解决方案,首先我们需要认清系统的高并发由3个层面导致: 1. 传输层 大量用户对系统请求后,将会造成网络带宽 ...

  7. SSM实战——秒杀系统之高并发优化

    一:高并发点 高并发出现在秒杀详情页,主要可能出现高并发问题的地方有:秒杀地址暴露.执行秒杀操作. 二:静态资源访问(页面)优化——CDN CDN,内容分发网络.我们把静态的资源(html/css/j ...

  8. Java并发测试

    要求:模拟200个设备,尽量瞬间并发量达到200. 思路 第一种:线程池模拟200个线程——wait等待线程数达200——notifyAll唤醒所有线程 第二种:线程池模拟200个线程——阻塞线程—— ...

  9. Java并发编程--5.信号量和障碍器

    Semaphore信号量 简介 它本质上是一个共享锁,限制访问公共资源的线程数目,它也被称为计数信号量acquire()许可一个线程, Semaphore – 1; 没有可用的许可时,Semaphor ...

随机推荐

  1. [程序员代码面试指南]递归和动态规划-换钱的最少货币数(DP,完全背包)

    题目描述 给定arr,arr中所有的值都为正数且不重复.每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim,求组成aim的最少货币数. 解题思路 dp[i][j]表示只用第0 ...

  2. vue父子组件状态同步的最佳方式续章(v-model篇)

    大家好!我是木瓜太香!一名前端工程师,之前写过一篇<vue父子组件状态同步的最佳方式>,这篇文章描述了大多数情况下的父子组件同步的最佳方式,也是被开源中国官方推荐了,在这里表示感谢! 这次 ...

  3. 关于px、pt、em、rem四个单位的解释

    写在前面 最近在群里突然看到一个问题,就是px pt em rem 三者的区别,这个问题看起来非常基础,也非常容易被忽略,however,面试会问到~,那我就解释一下 px px的英文是pixel,翻 ...

  4. docker部署LAMP架构并部署上线wordpress博客系统

    第一步:直接在镜像仓库拉取LAMP镜像 [root@ken-node3 ken]# docker pull tutum/lamp 第二步:查看已经获取到的镜像 [root@ken-node3 ken] ...

  5. jekins的搭建和踩过的坑

    https://www.cnblogs.com/jwrwst/p/6520114.html 主要按照上面的来装的 中间遇到过一些问题 https://wiki.jenkins.io/display/J ...

  6. Java并发包之ThreadPoolExecutor

    概述 ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等等服务. 构造参数 corePo ...

  7. idea快捷键壁纸

  8. 《Java从入门到失业》第四章:类和对象(4.4):方法参数及传递

    4.4方法参数及传递 关于这个知识点,我想了很久该不该在这里阐述.因为这个知识点稍微有点晦涩,并且就算不了解也不影响用Java编写代码.不过笔者刚开始工作的时候,就是因为这块内容没有过多的关注,以至于 ...

  9. 搭建Linux服务器

    工欲善其事必先利其器, 虚拟机:百度云链接地址:https://pan.baidu.com/s/1_nWQh3WKF7xLs5-nmbZ8lA   (Vmware 12 ) Linux 7:百度云链接 ...

  10. 剑指offer-递归和循环

    1. 斐波那契数列 解: 没啥好说的了,直接上高效的滚动迭代解法.矩阵解法和特征根解法这里不讨论了. class Solution: def Fibonacci(self, n): # write c ...