调优利器-火焰图使用图鉴
本来想起个题目叫 “什么?你还没用过这个工具?” 或者 “再见,火焰图”。但是想了想,己所不欲,勿施于人。
正常写个题目就好了,非搞这么多噱头,就是为了所谓的阅读量和点击量。
如果内容是干货,对人有帮助还好,要是满怀期待打开,进去划拉划拉,越看越不对劲,最后拉到末尾才近乎“上当了”,又是可恶的推广软文,这种感觉就如同吃了翔味的巧克力一样,让人反胃。
言归正传,今天来聊聊性能调优利器,火焰图的安装、使用及分析方法。
火焰图(Flame Graph)是由 Linux 性能优化大师 Brendan Gregg 发明的,和所有其他的 profiling 方法不同的是,火焰图以一个全局的视野来看待时间分布,它从底部往顶部,列出所有可能导致性能瓶颈的调用栈。
先看一个火焰图的样例,看不懂不要紧,后文会解释怎么去理解,稍安勿躁。
安装
要获得火焰图,需要安装一套组件,核心的组件主要有
async-profiler : 性能数据采集 FlameGraph : 性能数据分析,并生成火焰图
接下来按顺序讲解安装过程,读者朋友如果严格按照这个步骤来操作,一般都没有问题。
原料准备
至少需要有一套linux机器,笔者用的是centos-7。
可以搞个阿里云的ecs,也可以搞个虚拟机,当然直接在公司机器上操作也未尝不可,但是要注意安全,root权限下操作要小心(你懂的)。
环境准备
在正式安装之前,先确保环境已经准备好。否则环境搞了半天搞不定,直接放弃,打出GG。
至少保证jdk、perl、c++编译器已经安装完毕。
安装Java,不用多说了哇,网上教程一大堆。
yum -y install git
安装GCC编译器
yum install gcc gcc-c++
如果去搜索一下火焰图的其他教程,你可以会发现,有些教程让你安装 perf,我也试过了,装了perf,然后用perf-map-agent去搜集性能数据,直接失败了。
换成async-profiler,直接一次成功。不想踩坑的同学,直接用async-profiler吧,省心。
获取源码
很多教程都说,要安装git,然后使用git clone方式下载源码,但是他们没有交代的是,除了git安装外,你还需要经过配置才可以实现源码下载。
其实源码获取有更简便的方式,或许也不太简便吧,管他呢。
安装lrzsz,用于上传文件
yum install -y lrzsz
安装完成之后,获取async-profiler源码包。
进入async-profiler源码地址,https://github.com/jvm-profiling-tools/async-profiler, 微信禁止外链,读者可以自行复制粘贴至浏览器访问。
点击图中红线圈住的 “download zip” ,待下载完成之后,通过 sz -y 方式上传源码包。
使用相同的方式,将FlameGraph的源码包也上传至服务器。
进入FlameGraph源码地址,https://github.com/brendangregg/FlameGraph 下载源码压缩包,也一并上传至服务器。
具体的图不贴了,和上面一模一样。
安装async-profiler
接着就到重头戏了,首先介绍如何安装async-profiler。
进入async-profiler源码上传路径,解压源码(直接unzip async-profiler.zip),进入解压后的文件目录。
cd async-profiler
make
make完成之后,等待安装完成即可。
安装FlameGraph
实际上FlameGraph也无需安装,将代码上传并解压,就可以直接使用。
【实战】分析Java性能,生成火焰图
接着讲一个实战案例。
首先,要有一个分析目标程序,我在服务器上部署了一个基于netty的im聊天demo,同时启动服务端与客户端。
启动服务端:
nohup java -jar im-server-1.0.0-jar-with-dependencies.jar > Log.log 2>&1 &
启动客户端,指定服务端地址(客户端与服务端在同一个机器)
java -jar -Dremote_ip=127.0.0.1 im-client-1.0.0-jar-with-dependencies.jar
启动性能分析
启动性能分析,持续收集一分钟服务端性能指标。
先获取服务端的PID:
[root@snowalker ~]# ps -ef | grep java
root 9349 1 0 Feb11 ? 00:09:40 java -jar im-server-1.0.0-jar-with-dependencies.jar
PID为9349,接着启动async-profiler收集java进程的性能指标。
./profiler.sh -d 60 -o collapsed -f /tmp/test_01.txt ${pid}
简单解释下,
-d表示的是持续时长,60代表持续采集时间60s; -o表示的是采集规范,这里用的是collapsed; -f后面的路径,表示的是数据采集后生成的数据存放的文件路径(这里放在了/tmp/test_01.txt) ${pid} 表示的是采集目标进程的pid,也就是上面提到的30937
pid为进程实际的进程id,这里就是9349,那么只需要执行命令:
./profiler.sh -d 60 -o collapsed -f /tmp/test_02.txt 9349
运行期间,处于阻塞状态,直到约定时间完成。
运行期间,接着模拟用户聊天,连续发送消息至服务端:
性能数据收集结束之后,到/tmp/ 查看输出的性能指标文件:
[root@snowalker tmp]# ll
-rw-r--r-- 1 root root 6008 Feb 22 12:45 test_02.txt
可以看到,性能指标已经收集完成,接着就到火焰图生成工具-FlameGraph的出场时间了。
生成火焰图
执行命令
perl flamegraph.pl --colors=java /tmp/test_02.txt > /tmp/test_02.svg
查看tmp路径下文件:
[root@snowalker tmp]# ll
-rw-r--r-- 1 root root 32452 Feb 22 12:46 test_02.svg
-rw-r--r-- 1 root root 6008 Feb 22 12:45 test_02.txt
可以看到已经生成一张svg格式的图片,下载图片到本地:
sz test_02.svg
使用浏览器打开图片,一张美观的火焰图展现在面前:
如何阅读火焰图?
有了火焰图,我们得读懂它才能利用它进行性能优化。
一眼看过去,红红火火的,密密麻麻,可能你觉得案例中 不够密密麻麻,如果分析的是线上的程序,那复杂程度足够让人眼花缭乱。
那么我们应该如何理解火焰图的内容呢?
简单的说:
火焰图,每一列代表一个调用栈,每一个格子代表一个函数 纵轴,即垂直方向的y轴,展示了栈的深度,按照调用关系从下到上排列。最顶上格子代表采样时,正在占用 cpu 的函数。 横轴,即水平方向的x轴,表示:火焰图将采集的多个调用栈信息,通过按字母横向排序的方式将众多信息聚合在一起。需要注意的是它并不代表时间。【横轴没有特殊的含义,不代表调用关系!】 横轴格子的 宽度 代表其在采样中出现频率,所以一个格子的宽度越大,说明它是瓶颈原因的可能性就越大。 火焰图格子的颜色是随机的暖色调,方便区分各个调用信息。 其他的采样方式也可以使用火焰图, on-cpu 火焰图横轴是指 cpu 占用时间,off-cpu 火焰图横轴则代表阻塞时间。 采样方式可以是单线程、多线程、多进程甚至是多 host,进阶用法参考文档:https://www.brendangregg.com/flamegraphs.html
另外,多说两句,火焰图的栈深度与y轴高度成正比,可以这么认为:造成性能问题的基本都处于调用栈的栈顶位置。
因为栈顶位置的性能问题会间接拖慢整个调用栈,简单的举个例子:方法A调用方法B,方法B调用方法C。
如果方法C执行的慢则会间接导致方法B慢,从而导致方法A慢。符合我们说的通过分析栈顶从而达到分析瓶颈的目的。
如果A方法本身就慢呢?通过火焰图也是可以看出来的,这种底层栈的宽度很宽,但是建立在其撒花姑娘的调用链线条都很窄,火焰图呈现“┻”型,那么我们基本可以确定,栈底方法本身就存在性能问题。
我们的一个分析火焰图的基本原则就是,从栈顶看起,往栈底分析。