如何快速定位Java生产环境中的问题
前言
作为一名略懂Java的大数据开发,生产环境出问题几乎是家常便饭。在处理大数据量的开发前提下, 上线程序之后CPU 飙高、内存溢出、数据错乱 的问题时常发生。为了降低上线对系统的影响,通常时间窗口都在凌晨而且较短,这就要求我们具备快速定位和修复问题的能力。
思路
当生产环境出现问题的时候,首先要先确定问题的范围,并考虑以下问题:
- 这个问题有多严重? 是系统完全不可用,还是部分功能受影响?
- 所有用户都受影响,还是只有特定的请求有问题?
- 什么时候开始的? 是最近一次发版导致的,还是长期以来就有的?
- 是瞬时的,还是持续发生?
这些问题决定了后续排查的方向。
查看监控和日志
如果在本地的开发环境中,能够复现问题的话,我们可以复现问题。如果不能,我们可以依靠监控系统(如 Prometheus、Grafana等)的话,看看 CPU、内存、线程池、数据库连接池等指标 是否异常。
其次,查看系统或者程序中的日志(特别是 ERROR 级别的日志),生产环境通常会有 ELK(Elasticsearch + Logstash + Kibana),如果没有,也可以远程 SSH 连接服务器,使用 tail -f
监听最新日志。
# 查看最新的错误日志
tail -f /var/log/app.log | grep "ERROR"
然后在日志中找到异常信息,来确定是哪行代码出了问题,还是参数配置有问题。
服务器CPU负载过高
我曾经遇到过这样的问题:程序在开发环境和生产环境都没有问题,但是在运行一段时间之后,服务器的 CPU 就开始占用过高,96线程的CPU排队的任务(load)居然有200多,超出了CPU可处理的范围。这时候服务器的表现为:CPU 使用率 100%、线程卡死、程序响应慢。
我们通常使用服务器的命令和jvm的一些命令来排查:
- 使用
top
命令看看哪个 Java 进程 CPU 占用高 - 用
jstack
导出线程堆栈,查找 死循环、锁等待 - 使用
jstat
查看 GC 是否异常,是否在疯狂 Full GC,如果有的的话,使用jmap
命令或者jvisual可视化工具,查看哪个对象占用最多,然后再从代码中分析问题。
使用以下命令来来排查CPU负载问题。
代码语言:sh复制# 查找占用 CPU 最高的 Java 进程
ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%cpu | head
# 导出线程堆栈
jstack <pid> > thread_dump.log
# 查看gc
jstat -gcutil <pid>
如果是死循环,优化代码,如果是 GC 频繁,调整 JVM 参数,优化对象回收,如果是线程过多,优化线程池的使用。
内存溢出(OOM)
OOM是刚开始做开发的时候最常见的问题,其表现为:程序无法正常运行,并抛出 OutOfMemoryError: Java heap space
的异常,通过 jstat
查看gc情况,发现Full GC 频繁,导致GC Time过长。
其实归根结底就是程序本身因为一些问题,导致处理性能不够,很多对象无法被回收,GC Time越来越长,而且 GC 的时候程序是 STOP 状态,最后就导致恶性循环,出现OOM。
排查步骤:
- 使用
jmap -histo:live <pid>
查看哪些对象占用最多 - 导出堆内存 dump 文件
jmap -dump:format=b,file=heapdump.hprof <pid>
,用 MAT(Memory Analyzer Tool) 分析 - 检查是否有缓存未释放、死循环创建对象等问题
所以,在代码的开发的时候,我们要注意:
- 减少不必要的对象创建,避免大对象占用
- 调整 JVM 内存参数(-Xmx, -Xms)
- 检查是否有内存泄漏,如 List、Map 等不断增长
数据库慢查询
大数据分析中,和数据打交道比较多,经常会遇到以下问题:
- 数据查询页面加载慢,查询耗时很久
- 数据库 CPU 飙升
当遇到这个问题的时候,我们可以找到当前 SQL 操作的表,然后在数据库中进行分析:
- 执行
SHOW PROCESSLIST;
看看有没有慢 SQL - 通过
EXPLAIN
分析 SQL 执行计划,看看是不是全表扫描 - 查看表是否缺少索引,分析是否可以通过索引来提高查询速度。
针对于上面的问题,可以考虑以下的解决方案:
- 创建索引,避免全表扫描
- 使用缓存(Redis)缓存常用的数据,减少数据库压力,
- 分库分表,减少单库压力
结语
上面是开发上线过程中遇到过的部分问题,对于 Java 而言,日志 + 常用的linux命令 + jvm命令就可以定位出来大部分的问题。对于数据库或者组件,我们也可以通过日志和内置命令来分析问题。