一 背景

  在一次活动前的压测中,发现一个服务(平响为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. 乔悟空-CTF-i春秋-Web-SQL

    2020.09.05 是不是有些题已经不能做了--费了半天,到最后发现做不出来,和网上大神的方法一样也不行,最搞笑的有个站好像是别人运营中的,bug好像被修复了-- 做题 题目 题目地址 做题 尝试简 ...

  2. 送命题:讲一讲Mybatis插件的原理及如何实现?

    持续原创输出,点击上方蓝字关注我吧 目录 前言 环境配置 什么是插件? 如何自定义插件? 举个栗子 用到哪些注解? 如何注入Mybatis? 测试 插件原理分析 如何生成代理对象? 如何执行? 总结 ...

  3. 分布式系统监视zabbix讲解八之自动发现/自动注册

    自动发现(LLD) 概述 自动发现(LLD)提供了一种在计算机上为不同实体自动创建监控项,触发器和图形的方法.例如,Zabbix可以在你的机器上自动开始监控文件系统或网络接口,而无需为每个文件系统或网 ...

  4. 基于k8s的集群稳定架构

    前言 我司的集群时刻处于崩溃的边缘,通过近三个月的掌握,发现我司的集群不稳定的原因有以下几点: 1.发版流程不稳定 2.缺少监控平台[最重要的原因] 3.缺少日志系统 4.极度缺少有关操作文档 5.请 ...

  5. 虚拟机系列 | JVM类加载机制

    本文源码:GitHub·点这里 || GitEE·点这里 一.类加载简介 类的加载机制是指把编译后的.class类文件的二进制数据读取到内存中,并为之创建一个java.lang.Class对象,用来封 ...

  6. 机器学习-线性规划(LP)

    线性规划问题 首先引入如下的问题: 假设食物的各种营养成分.价格如下表: Food Energy(能量) Protein(蛋白质) Calcium(钙) Price Oatmeal(燕麦) 110 4 ...

  7. nginx安装步骤

    1.下载地址:下载nginx压缩包wget -c https://nginx.org/download/nginx-1.10.1.tar.gz2.配置nginx安装所需的环境yum install g ...

  8. JAVA MD5加密算法实现与原理解析

    public static String md5Encode(String inputStr) { MessageDigest md5 = null; try { md5 = MessageDiges ...

  9. 最精美详尽的 HTTPS 原理图!

      来源:r6a.cn/ffJk 作为一个有追求的程序员,了解行业发展趋势和扩充自己的计算机知识储备都是很有必要的,特别是一些计算机基础方面的内容,就比如本篇文章要讲的计算机网络方面的知识.本文将为大 ...

  10. Spring及tomcat初始化日志

    Tomcat StandardContext初始化过程 //org.apache.catalina.core.StandardContext#startInternal // 子容器启动 for (C ...