定位问题的先决条件

需要有详细的日志记录,提前告警的监控平台,事发现场保留

日志 :业务日志,中间件日志
监控 :CPU、内存、磁盘、网络,类加载、GC、线程等
快照 :-XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath

分析问题,解决问题的思路

经验+直觉,快速定位 > 逐一排查,传输链路 > 寻找规律 不要轻易怀疑监控。考虑资源。优先保证系统能正常运行。保留现场,事后排查定位问题。

逐一排查,传输链路,通过日志或工具逐一排查

  1. 内部原因,是否是客户端或者前端问题,程序发布后的Bug,回滚后可以立即解决
  2. 外部原因,比如服务,第三方服务,主机、组件的问题。
    1. 服务:错误日志邮件提醒或elk快速定位问题,查看gc日志
    2. 第三方服务:单独调用测试,联系第三方加急解决
    3. 主机: CPU相关问题,可以使用 top、vmstat、pidstat、ps 等工具排查; 内存相关问题,可以使用 free、top、ps、vmstat、cachestat、sar 等工具排查;IO 相关问题,可以使用 lsof、iostat、pidstat、sar、iotop、df、du 等工具排查;网络相关问题,可以使用 ifconfig、ip、nslookup、dig、ping、tcpdump、iptables等工具排查。
    4. 组件:查看日志输出,使用命令查看运行情况
  3. 因为系统资源不够造成系统假死的问题,通常需要先通过重启和扩容解决问题,之后再进行分析,系统资源不够,一般体现在 CPU 使用高、 内存泄漏或OOM 的问题、IO问题、网络相关问题这四个方面

分析问题的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jps -v 查看java进程
jinfo -flags pid 查看运行参数
jstat -gc 8544 5000 100,将每隔5s采样一次pid为8544的gc,输出100次

jmap -dump:live,format=b,file=dump.hprof 29170
#生成虚拟机的内存转储快照 注意线上可能会触发线上gc
jmap -heap 29170
jmap -histo:live 29170 | more
jmap -permstat 29170

jstack -l 29170 |more 显示虚拟机的线程快照

df -h # 磁盘
free -m / -h # 内存
top cpu # cpu

复制代码

线上cpu100%报警(找出最耗时CPU进程-线程-堆栈-代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
方法1:原生工具,慢
top -c #显示完整信息 P:cpu使用排序 M:内存使用排序
top -Hp 10765 ,#显示一个进程的线程运行信息列表 -H 显示线程信息,-p指定pid & P
printf "%x\n" 10804 转16进制 2f71
jstack 12084 | grep '0x2f71' -C5 --color 查看堆栈,找到线程在干嘛

方法2:
使用提前准备好的sh脚本,可以一条命名查看当前出事的线程代码,快,推荐
sh show-busy-java-threads.sh > a.txt #查询java耗时线程前5个
sh show-busy-java-threads.sh -p > a.txt #查询指定进程

方法3:
使用arthas,工具内置很多功能,比如可以查看源码,判断是否发布成功,可以用来排查疑难问题
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
dashboard
thread -8
jad com.xx.xx.xx.xxximp 查看线上类代码
watch com.xx.xx.xx.xxximp doTask '{params}' '#cost>100' -x 2
#观察会慢在什么入参上,监控耗时超过100毫秒的 doTask方法的入参,并且输出入参,展开2层入参参数
ognl #查询某静态字段的值

定位到堆栈就可以定位到出问题代码的行号,然后找对应的发布分支代码该行号即可
复制代码

线上内存OOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
某Java服务(假设PID=12084)出现了OOM,最常见的原因为:
1. 有可能是内存分配确实过小,而正常业务使用了大量内存
2. 某一个对象被频繁申请,却没有释放,内存不断泄漏,导致内存耗尽未调用close(),dispose()释放资源,例如:文件io,网络io
3. 某一个资源被频繁申请,系统资源耗尽,例如:不断创建线程(没有用线程池),不断发起网络连接等
总结:本身资源不够,申请资源太多,资源耗尽

分析工具:
jvisualvm(直方图),MAT(优先,直方图,跟踪内存使用的引用关系),JProfiler

线下分析:
服务挂掉之后有保留文件:直接下载dump文件导入mat分析
java -jar -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=

线上分析:
1. 确认是不是内存本身就分配过小
jmap -heap 12084

2. 找到最耗内存的对象
jmap -histo:live 12084 | head -n 10 #该命令会强制执行一次fgc

jmap -dump:format=b,file=/opt/dump.hprof {pid} #以二进制输出档当前内存的堆情况,
然后可以导入MAT等工具进行
tar –czf dump.tar.gz dump.hprof

3. 确认进程创建的线程数,以及网络连接数,如果资源耗尽,也可能出现OOM
ll /proc/17306/fd | wc -l
ll /proc/17306/task | wc -l
复制代码

如何防止线上问题发生

数据库:上线一个定时监控和杀掉慢SQL的脚本。这个脚本每分钟执行一次,检测上一分钟内,有没有执行时间超过一分钟(这个阈值可以根据实际情况调整)的慢SQL,如果有大事务自己觉得该阈值的合理性,如果发现,直接杀掉这个会话

cpu或者内存的使用率上做报警,大于90%的时候可以dump和jstack一次,甚至jstat也可以做,然后95%的时候也同样执行一次,甚至98或者99的时候也可以做一次,这样不仅可以保留现场,同时还可以对比

完善的服务报错日志监控,可选elfk+日志监控或sentry

完善的流程机制。完善的主机,中间件监控报警机制

遇到过的线上问题以及解决思路

Zuul 网关不响应任何请求,zuul假死

App打不开,请求超时,访问数据库超时,数据库cpu飙升有规律,在某个时间点才飙升,去调度中心找该时间断的的定时任务,排查是异步转账开多了线程导致的

工具汇总

参考