需求:服务器在某时段的CPU占用会规律性出现一段尖刺,现需每30s记录一次某服务器的系统资源使用状况,并且打印出CPU占用80%以上的线程堆栈信息,用于分析和优化。
jps命令 - 查看服务器当前进程
top命令 - 动态查看系统资源使用
显示进程id为1的CPU使用情况
top -Hp 1
其中,要关注的主要指标是%CPU和%MEM,cpu和内存使用情况。
如果发现某线程的CPU占用超过阈值,比如80%,可以取出其PID,即线程id然后使用jstack进行堆栈分析。
jstack 查看线程堆栈
jstack 1 用来查看 Java 进程中所有线程的堆栈信息。如果要查看某线程的情况,使用jstack ${PID}。
其中,nid是16进制的线程id,需要转换后和top命令显示的PID匹配,然后把使得CPU高占用的线程堆栈情况打印出来(代码在脚本中)。
再次分析一下需求:每30s使用top命令查看一次CPU使用情况,取出其中CPU%超过阈值的线程,jstack命令查看堆栈情况。
shell脚本
文件存在/data/top下
# 判断文件夹是否存在,不存在则创建
if [ ! -d "/data/top" ]; then
mkdir /data/top
fi
INTERVAL=30
# 在后台运行while循环
(
while true; do
# 根据时间定义文件名
now=$(date +%Y%m%d%H%M%S)
log_file=/data/top/top_${now}.log
jstack_log_file=/data/top/jstack_${now}.log
# 3天前数据清理
find /data/top -type f -mtime +3 -delete
JAVA_PID=$(pgrep -f "java")
# 保存top查询结果
touch ${log_file}
top -b -Hp ${JAVA_PID} -n 1 >> ${log_file}
# 查询CPU高占用线程
touch ${jstack_log_file}
top -b -n 1 -Hp ${JAVA_PID} | awk '$9 > 80.0 {print $1} ' | while read PID && [ -n "$PID" ]
do
# 将PID转换为16进制
hex_pid=$(printf '%x\n' $PID)
echo "Thread dump for PID : $PID, PID(In hex) : $hex_pid"
jstack $JAVA_PID | grep $hex_pid -A30 >> ${jstack_log_file}
done &
sleep $INTERVAL
done &
PID=$!
disown
echo "monitor Running in background, PID: $PID"
)
脚本可能存在的问题:在top命令和jstack命令之间有ms级的延迟,因此可能在top命令取出CPU%>80%的PID后,用jstack查看时,高CPU占用的步骤已经结束。
结论
将shell脚本在线上服务器在高峰时段运行,随机抽取log文件后分析,发现jstack文件里有效的几条是在打印日志。将kibana日志高峰时段相互印证,发现时间匹配,基本可以确定服务器高CPU%是由日志过多导致的。
在服务器上执行shell脚本的流程
# 1. 使用vi 创建文件
vi filename.sh
# 2. 把脚本内容复制进去
# 记得使用鼠标右键,因为ctrl+c,ctrl+v在console中不可用
# 3. 保存脚本
# esc 推出编辑模式,输入:wq ,按Enter进行保存,退出编辑器
# 4. 给脚本添加可执行权限
# chmod 改变文件权限; + x给文件添加可执行权限
chmod +x filename.sh
# 5. 运行脚本
./filename.sh
# 6. 记录PID并kill停止脚本
# 运行脚本后,会打印一个PID,如1694,再检查一下脚本是否正常运行
kill -9 1694
注意
- 如果vi编辑文件遇到冲突,需要先把当前文件相关线程kill掉,再修改
ps -ef | grep filename.sh
ps -ef
# 列出当前系统中运行的所有进程
grep filename.sh
# 筛选出含有 "filename.sh" 的行
- 查看文件 cat filename.sh
- 当使用cd /dir_name时,带有/是在根目录下,如果在当前文件夹下,则只需要cd dir_name