运维角度定位JAVA微服务线上CPU飙升问题


文章目录


前言

在实际生产环境中总会出现平均负载高的告警、CPU使用率超过100%的告警等情况。因此如何分析出现该情况的原因,也是运维在实际环境维护中所作的一部分,本篇文章就带领大家一步步定位到问题


一、常用的Load分析方法

Load指的是平均负载1、5、15

1.1.CPU高、Load高

yaml 复制代码
1、通过top命令查找占用CPU最高的进程PID
2、通过top -Hp PID 查找占用CPU最高的线程TID
3、对于java程序,查看进程的堆栈信息,使用jstack 进程PID打印进程的堆栈信息
4、通过printf %x tid 打印出最高消耗CPU线程的十六进制

1.2.CPU低、Load高

yaml 复制代码
产生的原因: 
	等待磁盘I/O完成的进程太多,导致进程队列长度过大,但是cpu运行的进程却很少。

排查过程:
	1、通过top命令查看CPU等待IO时间,即%wa
	2、通过iostat -d -x -m 1 10查看磁盘IO情况(yum -y install sysstat)
	3、通过sar -n DEV 1 10查看网络IO情况
	4、通过以下命令查找占用IO的程序
		ps -e -L h o state,cmd |awk '{if ($1=="R" || $1=="D"){print $0}}' |sort |uniq -c|sort -k 1nr

二、cpu高、Load高情况分析

使用vmstat 查看系统维度的CPU负载 使用top查看进程维度的CPU负载

1.使用vmstat查看系统维度的CPU负载

c 复制代码
vmstat -n 1 表示结果一秒刷新一次
结果:
[root@l-jdy-prod-bjxfj-nginx3 ~]# vmstat -n 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 258308      0 7391508    0    0     2     7    0    0  1  0 99  0  0
 0  0      0 258360      0 7391508    0    0     0     0  675  911  1  1 99  0  0
 0  0      0 259000      0 7391508    0    0     0    12  662  964  2  1 98  0  0
 0  0      0 259104      0 7391508    0    0     0    59  462  644  0  0 100  0  0
 0  0      0 259080      0 7391532    0    0     0     0  516  760  0  1 100  0  0
r: 表示系统中CPU等待处理的线程。由于CPU每次只能处理一个线程,所以该数值越大表示系统运行越慢
b: 表示阻塞的进程
us: 用户CPU时间,当us接近100,r队列会到达80
sy: 系统cpu时间,如果太高表示系统调用时间长,例如是IO频繁操作
wa: IO等待消耗的CPU时间百分比。该值较高时说明IO等待比较严重,可能磁盘大量作随机访问造成的,也可能是磁	盘性能出现了瓶颈
id: 处于空闲状态的CPU时间百分比。如果该值持续为0,同时sy是us的两倍,则通常说明系统则面临着CPU资源的短	  缺。
yaml 复制代码
常见问题即解决方法:
	1、如果r经常大于4,且id少于40,表示CPu负荷很重
	2、如果pi、po长期不等于0,表示内存不足
	3、如果disk常不等于0,且b中的队列大于3,表示IO性能不好

三、当CPU高时自动Dump堆栈信息

1.背景

c 复制代码
当CPU使用率过高时,自动生成堆栈转储(Dump)对于诊断问题是非常有帮助的。

常用的方法是使用jstack来获取线程堆栈信息,分析哪些线程占用了过多的CPU。
但是对于某些偶发性的CPU过高,研发人员不可能实时盯着系统,
当发现CPU告警后,在去上服务器上找到主机在操作dump,可能此时CPU已经恢复,没有及时获取到"第一现场"。
其方式是通过脚本方式在固定时间能主动查看CPU使用率,如果过高则可以执行Dump堆栈操作。

2.CPU高时自动Dump堆栈信息脚本

shell 复制代码
#!/bin/bash
# 可配置项:
# 触发dump的cpu阈值。default 70
	# STACK_DUMP_CPU_THRESHOLD=xxx
# 触发dump时列举的线程数(按使用率由高到低排列) default 10
	# STACK_DUMP_THREAD_COUNT=xxx
# 配置方式,使用行云分组的环境变量配置即可
# stack log 存放目录 /data/log/
# stack log 文件名: jstack_check_$(date +%Y%m%d%H%M%S).log
# 设置CPU阈值,当CPU使用率达到该阈值时触发线程快照
CPU_THRESHOLD="${STACK_DUMP_CPU_THRESHOLD:-80}"
THREAD_COUNT="${STACK_DUMP_THREAD_COUNT:-10}"
CPU_CORE=4
echo "Current CPU_THRESHOLD is $CPU_THRESHOLD"

JAVA_PID=$(pgrep -d, -x java)
echo "Current JAVA_PID is $JAVA_PID"

# 使用top命令获取当前CPU使用率,并提取其中的CPU利用率百分比
CPU_USAGE=$(top -b -n 1 | grep -A10 "PID USER" | grep java | grep "$JAVA_PID" | awk '{print $9}' | cut -d'.' -f1)
echo "Current Java($JAVA_PID) CPU_USAGE:$CPU_USAGE"

if [ -z "$JAVA_PID" ]; then
  echo "No Java process found."
  exit 1
fi
if [[ $CPU_USAGE -lt 0 ]]; then
  exit 1
fi

PER_CPU_USAGE=$[CPU_USAGE / CPU_CORE]
# 检查CPU使用率是否超过阈值
if [[ $PER_CPU_USAGE -gt $CPU_THRESHOLD ]]; then
  # 使用top命令查找占用CPU最高的前十个线程,并获取它们的信息
  TOP_THREADS=$(top -H -b -n 1 -p "$JAVA_PID" | grep -A$THREAD_COUNT 'PID USER' | head -n $THREAD_COUNT | grep -v 'PID')
  # 使用jstack捕捉JVM线程快照
  # 请将下面的Java进程ID替换为你要监视的Java进程的实际进程ID
  JSTACK_OUTPUT=$(/usr/local/jdk1.8.1_131/bin/jstack "$JAVA_PID")
  JSTACK_OUTPUT_FILE="/export/Logs/jstack_snapshot_$(date +%Y%m%d%H%M%S).log"

  echo "$JSTACK_OUTPUT" >>$JSTACK_OUTPUT_FILE
  echo "====top 10 stack as below:====" >>$JSTACK_OUTPUT_FILE
  echo "当前JAVA进程ID($JAVA_PID)CPU使用率:$PER_CPU_USAGE, $CPU_USAGE, $CPU_CORE"% >>$JSTACK_OUTPUT_FILE
  # 获取占用CPU最高的前十个线程的信息,包括线程的PID和堆栈信息,并将它们合并到同一行输出
  echo "Top ${THREAD_COUNT} CPU占用线程信息:" >>$JSTACK_OUTPUT_FILE
  while read -r THREAD_INFO; do
    THREAD_TID=$(echo "$THREAD_INFO" | awk '{print $1}')
    THREAD_NID=$(printf "%x\n" $THREAD_TID)
    THREAD_CPU_USAGE=$(echo "$THREAD_INFO" | awk '{print $9}')
    echo "线程TID: $THREAD_TID, THREAD_NID:$THREAD_NID, CPU使用率: $THREAD_CPU_USAGE%" >>$JSTACK_OUTPUT_FILE
  done <<<"$TOP_THREADS"
  #echo "捕捉了JVM线程快照并保存到 $JSTACK_OUTPUT_FILE"
fi

注意事项

yaml 复制代码
1、CPU使用率计算。因为我们服务往往都是多核,所以这个值会超过100。
而我们接到的CPU告警常常是单核高于某个阈值,基于此可以通过使用总使用率除以核数,就可以获取近似单核CPU使用率

2、top10高使用率输出仅打印线程ID(包括16进制和10进制的ID),打印每个线程的使用率值,方便快速方便高使用率线程。

输出示例:
JNI global references: 1820
====top 10 stack as below:====
当前JAVA进程ID(18406)CPU使用率:98, 393, 4%
Top 10 CPU占用线程信息:
线程TID: 19148, THREAD_NID:ac56, CPU使用率: 50.2%
线程TID: 384563, THREAD_NID:b2fd, CPU使用率: 23.5%
线程TID: 28173, THREAD_NID:2c56f, CPU使用率: 23.5%
线程TID: 4827, THREAD_NID:3619, CPU使用率: 23.5%
线程TID: 39587, THREAD_NID:48b0, CPU使用率: 17.6%
线程TID: 4257, THREAD_NID:c092, CPU使用率: 17.6%
线程TID: 195823, THREAD_NID:2c56c, CPU使用率: 17.6%
线程TID: 12468, THREAD_NID:2c56e, CPU使用率: 17.6%
线程TID: 8456, THREAD_NID:2c570, CPU使用率: 17.6%

注意事项:
	1、脚本添加好之后,是需要重新部署后才能生效
	2、检查脚本中的JDK的路径,和自己环境是否一致,检查参数:JSTACK_OUTPUT
	3、当前服务的CPU核数为多少,检查参数:CPU_CORE
	4、根据自身需求设置合理的触发dump最大CPU阈值,检查参数:CPU_THRESHOLD
	5、检查生成文件的路径是否存在,最好配置在可以自动清除的路径下,检查参数:JSTACK_OUTPUT_FILE

相关推荐
码字哥1 小时前
EasyExcel设置表头上面的那种大标题(前端传递来的大标题)
java·服务器·前端
m0_748257182 小时前
海康威视摄像头RTSP使用nginx推流到服务器直播教程
运维·服务器·nginx
Pou光明2 小时前
1_linux系统网络性能如何优化——几种开源网络协议栈比较
linux·运维·网络·网络协议·开源
fen_fen3 小时前
Docker如何运行一个python脚本Hello World
运维·docker·容器
TianyaOAO4 小时前
inmp+discuz论坛
linux·运维·服务器
customer084 小时前
【开源免费】基于SpringBoot+Vue.JS加油站管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·maven
寒月6584 小时前
黑盒白盒测试
运维·服务器
Hello.Reader4 小时前
Spring Retry 与 Redis WATCH 结合实现高并发环境下的乐观锁
java·redis·spring
海绵波波1074 小时前
zerotier实现内网穿透(访问内网服务器)
运维·服务器
helpme流水4 小时前
使用秘钥登录服务器
运维·服务器·github