java应用如何排查生产环境的线上问题

本教程的落脚点虽然是写的“java应用”,但是本质上这个排除线上问题的思路对于所有编程语言其实都是一样的。在本篇文章中,我们需要借助大量的第三方工具来窥探java内部应用的运行细节从而来判断发生故障的根本原因。我相信这些工具对于其他语言来说也应该是能找到类似的替代品的。同时需要指出的是本文主要针对java微服务架构的集群部署环境的情况下来说的。如果能够完整的接受到本文的全部信息,那么你就拥有处理线上问题独当一面的能力了!

线上环境的故障分类

对于不同的故障,处置的方法完全不一样所以必须分别对待。

  • 业务应用故障

因为代码编写错误导致的业务无法进行下去。比如空指针异常(NullPointException)、数组下标越界(IndexOutOfBoundsException)、类型转化异常(ClassCastException)等。这些都是由于代码写的不够健壮,导致的异常。或者说因为业务本身过于复杂,程序员并没有完全理解业务,导致线上环境运行的结果和预期不一样。这也是属于业务应用异常的一种。

  • jvm异常

基本表现上是cpu占用率达到100%,或者发生了内存溢出、Full Gc频率过高!

  • 数据库异常

数据库cpu100%,表锁死

  • 其他异常

比如中间件RabbitMq、Redis等

解决问题的基本思路

第一步:找到问题在哪里。

第二部:针对问题修正方案。

如果非要给这两个步骤加一个重要程度,那么我认为第一步占80%。

如果大家有看过《控制论与科学方法论》就会知道如果你要找到问题所在,那么你必须得了解清楚里面足够的信息。这本书里面举了一个例子,其实和我们计算机领域是一样的。

一个老教授正在通过化学反应研究一个新工艺(的确良,一种合成纤维),但是在实验的过程中一直没得到理想的实验结果,找了一个月也没有找到失败原因。和《控制论和科学方法论》的作者讨论后,发现老教授虽然对各类化学反应都有深的研究,但是他们进行实验的发生装置却是一个“黑盒子”,这意味着我们只知道最后的结果。中间的化学反应过程是完全不清楚的。在作者分析了之后,得出了一个结论:“很显然,为了控制反应,我们必须获得关于反应进行程度的足够的信息,并使信息系统构成负反馈体系”。第二天,老教授听从了作者的建议,对实验装置进行了改进,让实验进行的过程中能够实时的看到变化的信息以及信息的反馈。于是老教授一下就找到了问题的所在,并且成功的完成了实验。

上面这个例子主要是说明了一点,想要解决问题,你必须知道“黑盒子”中的足够信息。对于我们来说,web应用是一个黑盒子、jvm也是一个黑盒子,我们必须通过各种第三方工具内窥他们的正在进行的工作,以得到足够多的信息,然后才能找到具体的问题所在。

而如果能找到问题在哪里,基本上80%的工作就已经完成了。最害怕的情况是,问题出现了,你觉得莫名其妙,觉得问题无从找起,这个是最恐怖的。

排查问题

本文主要专注于如何找出问题的方法论,而不是帮你解决具体的问题。因为不幸的家庭各有各的不幸,我无法帮助每一个人解决具体的问题,但是只要你掌握了找问题的方法,那么解决问题就简单了。

1.业务应用故障

对于业务故障来说,最好用的内窥方法当然是日志了。一般来说我们会打印出这个应用中所有的INFO级别以上的日志,包括所有的sql都必须打印出来。对于集群部署的系统来说,可能日志地方比较分散,而且日志的体积会很大。如果真出了问题,那我们不可能登录每台机器上去查,这样的效率是极低的。所以一般都会用一些日志收集系统,把所有的日志都收集到同一个地方,并且提供日志搜索引擎。下面举几个例子:

  • 案例一

收到售后人员反馈:上午9点,新用户注册会提示“内部服务器”错误,请尽快解决问题。

很明显,用户注册的这个动作触发了一个exception,我们可以进入日志收集系统搜索指定时间范围内所有“exception”,一下就能看到相关的堆栈异常,定位到代码具体的位置。

  • 案例二

收到运营人员反馈:满减优惠券和无门槛优惠券在结账时应该可以同时使用,但是计算金额却只使用了“无门槛优惠券”,需要紧急修复这个问题。

这个案例和上一个案例性质上是不同的,他没有触发任何的异常,只是可能由于sql条件设置有误导致没有查出相关的记录。首先我们先根据用户id和时间还有其他关键字找到任意这个http请求日志。此时我们可以通过给每一个http线程赋予唯一的uuid并且在日志中显示。那么我们就可以通过这个uuid查出这个http请求所有相关的日志,包括sql。那么一下子就能查到具体是那条sql有问题,以及有什么问题了。

腾讯云日志收集

相关的日志收集系统:腾讯云日志收集阿里云日志收集elk

2.jvm异常

  • jvm的cpu占用持续100%

此时可以使用阿里巴巴出的工具arthas来诊断具体是那个线程占用cpu最高。下面的命令是帮你打印出在5秒内占用cpu最高的3个jvm线程。通过线程快照中的堆栈信息,一般可以找到根本问题。有可能是触发了无限循环;也有可能是数据异常或逻辑错误,导致了一个超大的循环(比如循环200万次)。或者是gc(垃圾回收)线程占用cpu最高,对于这种情况,我们需要再查看内存信息。

1
2
3
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
thread -n 3 -i 5000
  • jvm的内存溢出或者内存占用率很高

一般来说导致内存溢出或占用率很高的原因是产生了大量内存对象。场景的场景一般是select语句查出了过多的记录,内存已经装不下了(比如select语句没加条件,查出了1000万条记录)。也有的是因为在集合中不断的添加元素,但是集合又一直没有销毁(比如集合是一个静态变量)。当然具体的情况需要具体的分析,这个时候我们就需要找出到底是谁占用了内存,对内存做一个快照。

1
2
3
4
#找到java进程id(PID)
ps -ef | grep javajstack -l PID
#生成内存快照,其中heap.bin文件就是快照文件
jmap -dump:live,format=b,file=heap.bin PID

生成快照之后,下载并安装mat工具将刚刚生成的heap.bin文件导入进去。再点击内存泄露报告或者内存占用排行榜来分析问题。

mat内存泄露

当然有时候生成快照并不会很及时,当你发现问题的时候,你的应用程序已经被杀死了没来得及创建快照。这也不要紧,jvm有参数当发送内存溢出的时候,会自动生成内存快照。只需要在启动时使用如下参数:

1
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.bin -jar xxx.jar

3.数据库异常

数据库异常需要非常的重视,因为数据库挂了整个系统基本也就停摆了,优化数据库主要就是优化慢sql。

  • 打开慢sql日志,一般来说执行时间超过0.5秒的sql都是属于慢sql。
1
2
3
4
5
#修改my.conf文件,添加下面3行配置
slow-query-log=On
slow_query_log_file="mysql_slow_query.log"
log-query-not-using-indexes
long_query_time=0.5
  • 累计慢sql日志,一般是一天为单位。
  • 使用pt-query-digest工具分析生成的慢sql,该工具会根据所有满sql的总执行时间从多到少进行排序。

4.其他异常

有些异常可能比较奇怪,比如突然的系统的反应变慢,或者突然系统不可用过一段时间又恢复了。这个时候你可能需要链路追踪工具了。

目前类似的工具有:pinpoint、skywalking等,这里拿poinpoint来举例。 下图展示了调用时间的自然时间的关系图。

pinpoint

能够展示一个http请求的整个过程,包括执行的所有sql:

pinpoint

原文地址:https://www.jdkdownload.com/jdk_debug_online.html