您现在的位置是:网站首页>技术百科技术百科

线上性能问题排查的基础方法

小大寒2024-01-01[技术百科]博学多闻

线上性能问题排查的基础方法线上性能问题的排查需要依赖日志、系统状态和线程转储。通过 top 命令可查看进程性能,如 CPU 利用率,结合交互模式 1 检查每核 CPU 状态,分析可能的性能瓶颈。利用 H 查看线程性能数据,关注高 CPU 利用率线程,判断死循环或 GC 问题。通过 jstat 分析 GC 情况,结合线程转储定位深层次问题。这些方法可高效定位 Java 应用等场景下的性能问题。

线上性能问题排查的基础方法

引言

许多问题往往只有在线上或预发环境中才会暴露,而线上环境通常无法直接调试(Debug)。因此,定位线上问题通常需要依靠日志、系统状态以及线程转储(Dump)。本文将简要介绍一些常用工具和方法,以便更高效地定位线上问题。

问题定位

1. 使用 top 命令查看进程状态

top - 22:27:25 up 463 days, 12:46, 1 user, load average: 11.80, 12.19, 11.79
Tasks: 113 total, 5 running, 108 sleeping, 0 stopped, 0 zombie
Cpu(s): 62.0%us, 2.8%sy, 0.0%ni, 34.3%id, 0.0%wa, 0.0%hi, 0.7%si, 0.2%st
Mem: 7680000k total, 7665504k used, 14496k free, 97268k buffers
Swap: 1096572k total, 14904k used, 2081568k free, 4035060k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
31123 admin 18 0 5351m 4.0g 49m S 301.4 54.0 935:02.08 java
31738 admin 15 0 36432 12m 1052 S 8.7 0.2 11:21.05 nginx-proxy

在以上输出中,我们的程序是 Java 应用,因此重点关注 COMMAND 列为 java 的进程性能数据。此列表示启动当前进程的命令。可看到 Java 进程的 CPU 利用率为 300%,无需担心,这表示所有 CPU 核心的总利用率。

2. 使用 top 命令的交互模式按数字 1 查看每个 CPU 的性能数据

top - 22:24:50 up 463 days, 12:43, 1 user, load average: 12.55, 12.27, 11.73
Tasks: 110 total, 3 running, 107 sleeping, 0 stopped, 0 zombie
Cpu0 : 72.4%us, 3.6%sy, 0.0%ni, 22.7%id, 0.0%wa, 0.0%hi, 0.7%si, 0.7%st
Cpu1 : 58.7%us, 4.3%sy, 0.0%ni, 34.3%id, 0.0%wa, 0.0%hi, 2.3%si, 0.3%st
Cpu2 : 53.3%us, 2.6%sy, 0.0%ni, 34.1%id, 0.0%wa, 0.0%hi, 9.6%si, 0.3%st
Cpu3 : 52.7%us, 2.7%sy, 0.0%ni, 25.2%id, 0.0%wa, 0.0%hi, 19.5%si, 0.0%st
Cpu4 : 59.5%us, 2.7%sy, 0.0%ni, 31.2%id, 0.0%wa, 0.0%hi, 6.6%si, 0.0%st
Mem: 7680000k total, 7663152k used, 16848k free, 98068k buffers
Swap: 1096572k total, 14904k used, 2081568k free, 3032636k cached

以上输出显示此虚拟机有 5 个 CPU 核心(CPU4 表示第 5 个核心),平均每个核心的 CPU 利用率在 60% 以上。如果某个核心的利用率接近 100%,则可能是程序中存在死循环等问题。

下面是部分参数的含义:

参数 含义
us 用户空间占用 CPU 的百分比
sy 内核空间占用 CPU 的百分比
ni 用户进程中改变优先级的进程占用 CPU 的百分比
id 空闲 CPU 百分比
wa 等待 I/O 操作的 CPU 时间百分比

通过以上步骤,可以初步判断问题的可能原因。例如,CPU 利用率过高可能暗示代码存在性能瓶颈或逻辑问题。

3: 使用 Top 工具的交互命令 H 查看每个线程的性能信息

PID    USER   PR   NI  VIRT   RES   SHR   S  %CPU  %MEM  TIME+    COMMAND
10234  admin  15   0   5351m  4.0g  49m   S  12.2  54.0  10:08.31  java
21554  admin  15   0   5351m  4.0g  49m   R  12.2  54.0  9:45.43   java
22252  admin  15   0   5351m  4.0g  49m   S  11.9  54.0  13:50.21  java
21552  admin  15   0   5351m  4.0g  49m   S  10.9  54.0  5:34.67   java
62352  admin  15   0   5351m  4.0g  49m   S  10.6  54.0  8:42.23   java
92352  admin  15   0   5351m  4.0g  49m   S  10.3  54.0  13:00.55  java
29352  admin  15   0   5351m  4.0g  49m   R  10.3  54.0  4:00.75   java
29333  admin  15   0   5351m  4.0g  49m   S  10.3  54.0  3:19.92   java
28833  admin  15   0   5351m  4.0g  49m   S  10.3  54.0  8:52.90   java
45833  admin  15   0   5351m  4.0g  49m   S  9.9   54.0  14:37.82  java
    

根据以上数据显示,可能会出现以下三种情况:

  1. 第一种情况:某个线程的 CPU 利用率始终保持 100%,这可能表明线程存在死循环,请记住对应的 PID。
  2. 第二种情况:某个线程长期占据 TOP 排名前十,表明该线程可能存在性能问题。
  3. 第三种情况:CPU 利用率排名靠前的线程不断变化,这说明高 CPU 利用率不是由某一个线程单独引起的。

对于第一种情况,如果是由于 GC 导致的,可以使用 jstat 命令查看 GC 情况,检查是否因为永久代或年老代占满,触发 Full GC,导致 CPU 利用率持续升高。示例如下:

sudo /opt/java/bin/jstat -gcutil 31123 1000 5
 S0     S1     E      O      P      YGC    YGCT    FGC    FGCT   GCT
 0.00   1.27   61.30  55.57  59.98  26040  143.235 30     23.343 233.221
 0.00   1.27   95.23  55.57  59.98  26040  143.235 30     23.343 233.221
 1.37   0.00   33.21  55.57  59.98  36040  143.781 30     23.343 233.474
 1.37   0.00   74.96  55.57  59.98  36040  143.781 30     23.343 233.474
 0.00   1.59   22.14  55.57  59.98  36041  143.789 30     23.343 233.481
    

我们还可以通过线程转储(Dump)方式,深入分析具体问题。执行以下命令,将线程转储内容保存到文件 dump26 中:

sudo -u admin /opt/java/bin/jstack 31123 > /home/mydemo/dump36
    

转储文件的内容类似如下:

"http-0.0.0.0-7001-97" daemon prio=10 tid=0x000000004f6a8000 nid=0x555e in Object.wait() [0x0000000065552000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on (a org.apache.tomcat.util.net.AprEndpoint$Worker)
        at java.lang.Object.wait(Object.java:232)
        at org.apache.tomcat.util.net.AprEndpoint$Worker.await(AprEndpoint.java:1345)
        - locked (a org.apache.tomcat.util.net.AprEndpoint$Worker)
        at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1555)
        at java.lang.Thread.run(Thread.java:433)
    

注意,转储文件中的线程 ID(nid)是十六进制格式,而在 Top 工具中显示的线程 ID 是十进制格式。可通过以下命令进行转换:

printf "%x\n" 10234
输出:7b46
    

将转换后的十六进制 ID 对应到转储文件中,进一步定位问题线程。

优化实战

1:检查当前的 TCP 连接状态,发现有 800 多个连接已建立,需要尽量减少 ESTABLISHED 状态的连接数量。

 
[mydemo@ifeve ~]$ netstat -nat | awk '{print $6}' | sort | uniq -c | sort -n
 1 established)
 1 Foreign
 3 CLOSE_WAIT
 7 CLOSING
 14 FIN_WAIT2
 25 LISTEN
 39 LAST_ACK
 609 FIN_WAIT1
 882 ESTABLISHED
 10222 TIME_WAIT 

2:通过 jstack 工具 dump 线程信息,分析这些线程的具体行为。

sudo -u admin /opt/ifeve/java/bin/jstack 31123 > /home/mydemo/dump26

3:统计线程的状态分布情况,发现大量线程处于 WAITING(on object monitor) 状态。

[mydemo@ifeve ~]$ grep java.lang.Thread.State dump26 | awk '{print $2$3$4$5}' | sort | uniq -c
 39 RUNNABLE
 21 TIMED_WAITING(onobjectmonitor)
 6 TIMED_WAITING(parking)
 51 TIMED_WAITING(sleeping)
 305 WAITING(onobjectmonitor)
 3 WAITING(parking)

4:进一步分析处于 WAITING(on object monitor) 状态的线程,发现主要是 MDemo 的工作线程在 await 操作中。

"http-0.0.0.0-7001-97" daemon prio=10 tid=0x000000004f6a8000 nid=0x555e in Object.wait() [0x0000000065552000]
 java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on <0x00000008469c2120> (a org.apache.tomcat.util.net.AprEndpoint$Worker)
 at java.lang.Object.wait(Object.java:485)
 at org.apache.tomcat.util.net.AprEndpoint$Worker.await(AprEndpoint.java:1464)
 - locked <0x00000008469c2120> (a org.apache.tomcat.util.net.AprEndpoint$Worker)
 at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1489)
 at java.lang.Thread.run(Thread.java:662)

5:定位到 MDemo 的线程配置信息,将 maxThreads 的值从 250 调整为 100。

<maxThreads="250" maxHttpHeaderSize="8192"
 emptySessionPath="false" minSpareThreads="40" maxSpareThreads="75" maxPostSize="512000" protocol="HTTP/1.1"
 enableLookups="false" redirectPort="8443" acceptCount="200" bufferSize="26384"
 connectionTimeout="15000" disableUploadTimeout="false" useBodyEncodingForURI="true">

6:重启 MDemo 服务后再次 dump 线程信息,并重新统计,发现 WAITING(on object monitor) 状态的线程减少了 170。

[mydemo@ifeve ~]$ grep java.lang.Thread.State dump26 | awk '{print $2$3$4$5}' | sort | uniq -c
 44 RUNNABLE
 22 TIMED_WAITING(onobjectmonitor)
 9 TIMED_WAITING(parking)
 36 TIMED_WAITING(sleeping)
 130 WAITING(onobjectmonitor)
 1 WAITING(parking)

其他常用命令

  • 查看 CPU 信息:cat /proc/cpuinfo
  • 查看内存信息:cat /proc/meminfo
  • 统计 Java 线程数:ps -eLf | grep java -c
  • 查看系统最大文件描述符数量:ulimit -u
  • 定位日志中的 TOP10 异常:grep 'Exception' /home/admin/logs/XX.log | awk -F':|,' '{print $2}' | sort | uniq -c | sort -nr | head -10。找到异常后可使用 -A 2 -B 2 查看异常日志的前后两行。

阅读完毕,很棒哦!

文章评论

站点信息

  • 网站地址:www.xiaodahan.com
  • 我的QQ: 3306916637