一 背景

  在一次活动前的压测中,发现一个服务(平响为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. [LeetCode]394. 字符串解码(栈)

    题目 给定一个经过编码的字符串,返回它解码后的字符串. 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次.注意 k 保证为正整数. ...

  2. JVM--堆是分配对象的唯一选择么?

    在<深入理解Java虚拟机>中关于Java堆内存有这样一段描述:随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配.标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐 ...

  3. Node.js 从零开发 web server博客项目[项目介绍]

    web server博客项目 Node.js 从零开发 web server博客项目[项目介绍] Node.js 从零开发 web server博客项目[接口] Node.js 从零开发 web se ...

  4. 吾日三省吾身 java核心代码 高并发集群 spring源码&思想

    阿里面试题    未解决https://my.oschina.net/wuweixiang/blog/1863322 java基础  有答案  https://www.cnblogs.com/xdp- ...

  5. 4.Strom-可靠性保证

  6. php第四天-字符串

    0x01 字符串 1.1 字符串的处理方式 在不同的语言中,字符串的处理方式不同:在C中字符串是作为字节数组处理的:在Java中字符串是作为对象处理的:而在php中则把字符串作为基本数据类型来处理. ...

  7. 把VS Code打造成Java开发IDE

    近期,公司推行正版化,本人使用的是JetBrains教育版,是不允许进行商业开发的,因此开启了艰难的备用IDE选型之路.最终,我选定了轻量级的Visual Studio Code(以下简称VS Cod ...

  8. 2020DASCTF八月浪漫七夕战

    安恒大学 注入点在邮箱注册那里,无法复现了,提一下 ezflask 源代码 #!/usr/bin/env python # -*- coding: utf-8 -*- from flask impor ...

  9. phpStudy8.1.0.1配置子域名多网站

    版本 这里phpStudy版本为8.1.0.1: 步骤 假设域名为:domain.com:公网IP地址为:42.33.33.33 首先云解析中配置,添加子域名A记录直接指向你的公网IP: ep.dom ...

  10. Spark 模型选择和调参

    Spark - ML Tuning 官方文档:https://spark.apache.org/docs/2.2.0/ml-tuning.html 这一章节主要讲述如何通过使用MLlib的工具来调试模 ...