前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >我的性能分析之路启程

我的性能分析之路启程

作者头像
云云众生s
发布2024-05-29 12:15:20
610
发布2024-05-29 12:15:20
举报
文章被收录于专栏:云云众生s云云众生s

回顾我的职业生涯,中间件技术专家这个角色持续的时间最长,关于性能分析经历了许多,但故事要从我顿悟的那个案例开始。

某集团省公司新系统上线

该公司计划将核心业务升级到 WebService 架构。通过 WebService 将应用前后端分离。这里说的前端和后端都是运行在 WebLogic 的 Java Web 程序,只是前端负责生成 Web 页面,而后端则是负责执行业务逻辑。

可以想象,这样激进的架构不仅开发成本很高,也可能面临许多未知的风险。临近上线执行性能测试时,果然遇到了问题。新采购的小型机 CPU 已经拉满,但是几十个节点的集群才能达到几百的吞吐量。于是这个公司找到了我们提供支持,他们怀疑 WebLogic 需要调优。

可能很多年轻的朋友也不知道什么是 WebService,简单说就是一种分布式计算技术。和现在基于 JSON 的 API 很像,但报文封装用的是 SOAP,SOAP 是一种非常复杂的 XML。这个技术当年被许多大厂商推崇,但现在已经不太常见了,这里就不分析原因了,通过阅读本文你可能会对这个技术有一定的感受。

再讲讲我当时的情况。我是 Java 程序员出身,工作中接触到了 WebLogic,基本管理配置还是比较熟悉的。我也组织过大规模的性能测试,对于各类瓶颈有一定的认识。不过因为测试的对象都是成熟的系统,性能达标后就没有进一步优化,并没有在调优上做更多的工作。因此,这次支持对我来说可能也是一个挑战。

一般情况下,CPU 拉满是好现象,整个应用的瓶颈是 CPU 了,说明已经已经发挥了硬件的完全性能,可以暂时不考虑性能相关的其他因素。那么是什么占用了 CPU?当时的工具还不健全,也可能是我技能还不够完整,不过我觉得有一个思想是永远不会变的,需要更清楚地看到问题

是应用占据了 CPU 吗?

确认这一点很容易,top、ps 命令都可以,很明显是 WebLogic 的 Java 程序占据的 CPU 最高。

哪些线程 CPU 高?

这里的操作系统是 AIX,我知道可以用 ps 命令查看线程的 CPU 占用率:

代码语言:javascript
复制
[/app/weblogic/bea/user_projects/domains/kfapp_domain]$ps -mp 172540 -o THREAD
    USER    PID   PPID      TID ST  CP PRI SC    WCHAN        F     TT BND COMMAND
weblogic 172540 119578        - A  1083  60 203        *   242001  pts/0   - /usr/java5_64/bin/java -Xms512m 
       -      -      -   377063 S    0  82  1 1190361d8   c10400      -   - -
       -      -      -   401657 S    2  83  1 f100070f10006240  8410400      -   - -
       -      -      -   405745 S    0  62  1 f100070f10006340  8410404      -   - -
       -      -      -   409615 R    8  60  1        -   400000      -   - -
       -      -      -   417831 S    1  82  1 f100070f10006640  8410400      -   - -
       -      -      -   434239 S    2  83  1 11cb85168   c10400      -   - -
       -      -      -   446625 S    0  82  1 f100070f10006d40  8410400      -   - -
       -      -      -   450639 S    0  82  1 f100070f10006e40  8410404      -   - -
       -      -      -   471155 S    0  82  1 f100070f10007340  8410404      -   - -
       -      -      -   475273 S    2  83  1 11cb85168   c10400      -   - -
       -      -      -   491667 R    7  60  1 11487ec88   c00000      -   - -
       -      -      -   508127 S    6  86  1 11cb85168   c10400      -   - -
       -      -      -   524491 S    0  66  1 f100070f10008040  8410400      -   - -
       -      -      -   528603 S   10  88  1 11cb85168   c10400      -   - -
       -      -      -   532703 S    0  82  1 1190361d8   c10400      -   - -
       -      -      -   545005 R   21  60  1 11487ec88   c00000      -   - -

从 CP 字段可以看到,并没有某个或某些线程的占用的 CPU 较高,难道就是应用本身慢吗?

作为 Java 应用中间件产品专家,对于产品的了解固然重要,但另一个很重要的难点是如何证明不是中间件的问题。应用运行在中间件上,还可能要与其他外围系统交互,很多问题并不是中间件产品的问题,但是如何证明呢?

进一步分析

我知道,我还有个 Java 工程师最常用的工具:线程堆栈。因为这里用的是 AIX,其 JDK 为 IBM JDK 。通过 kill -3 [pid] 我们就可以在 WebLogic 根目录得到一个 javacore 开头的文件,也就是线程堆栈文件,线程堆栈就是 Java 进程的线程快照。

我用 kill -3 得到了几个快照,但结论并不是那么一目了然,大多数工作线程都处于类似下面的状态:

代码语言:javascript
复制
at java/util/zip/ZipFile.getEntry(Native Method) 
at java/util/zip/ZipFile.getEntry(ZipFile.java:292(Compiled Code)) 
at weblogic/utils/classloaders/ClasspathClassFinder.getZipSource(ClasspathClassFinder.java:360(Compiled Code)) 
at weblogic/utils/classloaders/ClasspathClassFinder.getSourcesInternal(ClasspathClassFinder.java:296(Compiled Code)) 
at weblogic/utils/classloaders/ClasspathClassFinder.getSource(ClasspathClassFinder.java:272(Compiled Code)) 
at weblogic/utils/classloaders/JarClassFinder.getSource(JarClassFinder.java:46(Compiled Code)) 
at weblogic/utils/classloaders/MultiClassFinder.getSource(MultiClassFinder.java:64(Compiled Code)) 
at weblogic/utils/classloaders/MultiClassFinder.getSource(MultiClassFinder.java:64(Compiled Code)) 
at weblogic/utils/classloaders/MultiClassFinder.getSource(MultiClassFinder.java:64(Compiled Code)) 
at weblogic/utils/classloaders/CodeGenClassFinder.getSource(CodeGenClassFinder.java:33(Compiled Code)) 
at weblogic/utils/classloaders/MultiClassFinder.getSource(MultiClassFinder.java:64(Compiled Code)) 
at weblogic/utils/classloaders/CodeGenClassFinder.getSource(CodeGenClassFinder.java:33(Compiled Code)) 
at weblogic/utils/classloaders/GenericClassLoader.findResource(GenericClassLoader.java:212(Compiled Code)) 
at weblogic/utils/classloaders/GenericClassLoader.getResourceInternal(GenericClassLoader.java:143(Compiled Code)) 
at weblogic/utils/classloaders/GenericClassLoader.getResource(GenericClassLoader.java:169(Compiled Code)) 
at java/lang/ClassLoader.getResourceAsStream(ClassLoader.java:482(Compiled Code)) 
at weblogic/apache/xerces/util/SecuritySupport12$4.run(SecuritySupport12.java:119(Compiled Code)) 
at java/security/AccessController.doPrivileged(AccessController.java(Compiled Code)) 
at weblogic/apache/xerces/util/SecuritySupport12.getResourceAsStream(SecuritySupport12.java(Compiled Code)) 
at weblogic/apache/xerces/util/ObjectFactory.findJarServiceProvider(ObjectFactory.java:299(Compiled Code)) 
at weblogic/apache/xerces/util/ObjectFactory.createObject(ObjectFactory.java:191(Compiled Code)) 
at weblogic/apache/xerces/util/ObjectFactory.createObject(ObjectFactory.java:121(Compiled Code)) 
at weblogic/apache/xerces/parsers/DOMParser. (DOMParser.java:157(Compiled Code)) 
at weblogic/apache/xerces/parsers/DOMParser. (DOMParser.java:140(Compiled Code)) 
at weblogic/apache/xerces/jaxp/DocumentBuilderImpl. (DocumentBuilderImpl.java:103(Compiled Code)) 
at weblogic/apache/xerces/jaxp/DocumentBuilderFactoryImpl.setAttribute(DocumentBuilderFactoryImpl.java:126(Compiled Code)) 
at weblogic/xml/jaxp/WebLogicDocumentBuilderFactory. (WebLogicDocumentBuilderFactory.java:49(Compiled Code)) 
at weblogic/xml/jaxp/RegistryDocumentBuilder.getDefaultDocumentBuilderFactory(RegistryDocumentBuilder.java:283(Compiled Code)) 
at weblogic/xml/jaxp/RegistryDocumentBuilder.getDocumentBuilder(RegistryDocumentBuilder.java:222(Compiled Code)) 
at weblogic/xml/jaxp/RegistryDocumentBuilder.parse(RegistryDocumentBuilder.java:147(Compiled Code)) 
at org/apache/axis/utils/XMLUtils.newDocument(XMLUtils.java:322(Compiled Code)) 
at org/apache/axis/utils/XMLUtils.newDocument(XMLUtils.java:335(Compiled Code)) 
at org/apache/axis/configuration/FileProvider.configureEngine(FileProvider.java:209(Compiled Code)) 
at org/apache/axis/AxisEngine.init(AxisEngine.java:187(Compiled Code)) 
at org/apache/axis/AxisEngine. (AxisEngine.java:172(Compiled Code)) 
at org/apache/axis/client/Service.getAxisClient(Service.java:143(Compiled Code)) 
at org/apache/axis/client/Service. (Service.java:152(Compiled Code)) 
at com/xxx/esb/action/webservice/ZJESBserviceServiceLocator. (ZJESBserviceServiceLocator.java:10(Compiled Code)) 
at com/yyy/integration/eai/xxxFacadeESB.getxxxESBStub(xxxFacadeESB.java:33(Compiled Code)) 
at com/yyy/integration/eai/xxxFacadeESB.doBussiness(xxxFacadeESB.java:177(Compiled Code)) 
at com/yyy/integration/eai/xxxESBServiceBean.doBussiness(xxxESBServiceBean.java:29(Compiled Code)) 
at com/yyy/integration/ejb/BusinessServiceBean.searchBusiNumLinkOrderNum(BusinessServiceBean.java:2122(Compiled Code)) 

...

大量线程处于这样的状态。堆栈最后是 JDK 和 WebLogic 的方法,是否意味着 JDK 或 WebLogic 本身有性能问题?一个有经验的工程师其实会反问,如果基础的类库有问题,大概率早就有人报告了,所以还是看看自己哪里有问题。

这个思考过程其实也告诉我们,应该等某个产品的大版本成熟后再选用,这样应该可以避开那些严重的软件 Bug 。

此时此刻,如果我跟这个客户和开发商说是他们应用的问题,估计不会得到太正面的回应,毕竟证据不明显,而开发商和客户的技术水平可能更加无法理解这些现象。

那时的我的知识水平也比较有限,所以只能从现有的数据中寻找线索,于是我做了一个后来看来非常正确的动作,我将所有的线程堆栈做了一个统计,形成以下表格:

正在执行的方法

线程数量

百分比

java/util/zip/ZipFile.getEntry(Native Method)

33

16%

java/util/zip/ZipFile.getEntry(ZipFile.java:290(Compiled Code))

24

12%

com/yyy/util/UUID.(UUID.java:81(Compiled Code))

24

12%

而且这些堆栈都指向了同一个方法:

代码语言:javascript
复制
at org/apache/axis/client/Service.getAxisClient(Service.java:143(Compiled Code)) 

突然之间,我做开发时的一个记忆闪现了出来:对于 EJB 操作,因为 EJB Stub 的创建非常耗时,应该在客户端进行缓存。这个 Axis 的客户端,是否也有类似的问题?

解决问题

与应用开发人员一起找到了这段代码对应的应用代码,发现果然是每一次请求都会新建一个 Axis 客户端。于是修改代码,将 Axis 客户端存放到了 ThreadLocal 上,又重新进行了测试。效果相当的惊人,吞吐量轻松提高了 10 倍以上。

什么是 Profiling 工具?

这次问题处理,帮客户解决了大问题,对我个人也有着重大的意义,从此我对这个行业有了比较充分的信心。

不久之后我进了 IBM,主要是做 WebLogic 和 WebSphere Application Server (WAS)的支持。WAS 有着更加健全的诊断体系,例如 WAS 的 MustGather 网站详细说明了不同的故障应该收集的信息,以及处理方法。这让我有机会更系统地学习软件的问题诊断。

因为当时 AIX 的市场占有率很高,所以接触到的工具也是 AIX 的。通过学习和实践,我发现了 tprof 这个工具,例如通过执行命令 tprof -kesj -x sleep 60,我们可以得到以下报告:

代码语言:javascript
复制
...
Shared Object                                                             %  
=============                                                          ======
/usr/java6_64/jre/lib/ppc64/default/libj9prt24.so                       38.23
/usr/java6_64/jre/lib/ppc64/default/libjclscar_24.so                    26.27

...
 Total % For All Processes (/usr/java6_64/jre/lib/ppc64/default/libj9prt24.so) = 38.23

Subroutine                                            %   Source              
==========                                         ====== ======              
rt_time_TEXT                                        27.92 rt_time.s           
.j9time_current_time_millis                         10.32 aix/j9time.c        

Profile: /usr/java6_64/jre/lib/ppc64/default/libjclscar_24.so

  Total % For All Processes (/usr/java6_64/jre/lib/ppc64/default/libjclscar_24.so) = 26.27

Subroutine                                            %   Source              
==========                                         ====== ======              
.JIT_java_lang_System_currentTimeMillis             20.38 jlsys.s             
._ptrgl                                              5.89 ptrgl_64.s 
...

这个报告是 60 秒的时间里 CPU 被程序占用的一个统计报告。可以看到 38.23 的 CPU 时间在执行动态链接库 libj9prt24.so 的程序,具体来说 27.92% 是在执行 rt_time_TEXT 这个方法。

这是取自另一个故障的数据,应用中大量的获取系统时间的操作严重的影响了系统的运行效率。

如果我早点知道 tprof ,我可能可以更好地完成本文所说的那次问题诊断,其实我已经自发的用 tprof 的思想执行了 profiling。

tprof 命令执行时,每隔一定的时钟周期, 便会记录正在执行的指令,然后将所有的记录进行统计分析,形成报告。报告中出现较多的指令,通常也是占用 CPU 最多的指令,从而我们可以判断哪些方法,哪些组件可能存在性能问题。

现在大家都知道火焰图,这正是 profiling 工具收集数据的一种图形化展示方式:

其他工具

随着 Linux 的占有率越来越高,我们更多的使用 perf 命令实现 profiling。许多新兴的编程语言,也内置了更好的工具,例如 go 语言的 pprof。随着云原生的发展,还有 eBPF 的出现,又有人提出了 continuous profiling 这个概念。例如 Pyroscope ,通过这样的工具,我们可以随时随地实现 profiling 。

后记

职业生涯早期的这个关键之战对我意义重大。在那短短的一周里,我解决了两个关键问题,让项目能够顺利上线,赢得了合作伙伴的信任。更重要的是,这次支持也给了我信心,促使我完善自己的知识体系,让我能够更自信从容地应对未来的挑战。

不过,自此我也发现,这个行业有太多不专业的人。许多时候,即使你解决了问题,可能也无法得到感谢。例如这个客户的领导其实完全不懂技术,她并不能判断谁对谁错,谁的贡献更大,但是却有一股子不知道来自何处的傲慢。多年之后又遇到了这位领导,态度依旧,实在是不想跟这种人合作了。以后有机会跟大家聊聊工作生涯中遇到的奇葩事情奇葩人,顺便聊聊技术。

相关文章:

  1. PGQ:Go语言中基于Postgres的长时间运行作业排队
  2. CheerpJ 3.0: 在浏览器中用WebAssembly运行应用
  3. Java 21 即将发布,但 JavaOne 呢?
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-282,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 某集团省公司新系统上线
  • 是应用占据了 CPU 吗?
  • 哪些线程 CPU 高?
  • 进一步分析
  • 解决问题
  • 什么是 Profiling 工具?
  • 其他工具
  • 后记
    • 相关文章:
    相关产品与服务
    消息队列 TDMQ
    消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档