一次线上问题排查所引发的思考

标签: Java 进阶 Java Thread concurrent JVM | 发表时间:2018-07-08 19:10 | 作者:
出处:http://crossoverjie.top/

前言

之前或多或少分享过一些 内存模型对象创建之类的内容,其实大部分人看完都是懵懵懂懂,也不知道这些的实际意义。

直到有一天你会碰到线上奇奇怪怪的问题,如:

  • 线程执行一个任务迟迟没有返回,应用假死。
  • 接口响应缓慢,甚至请求超时。
  • CPU 高负载运行。

这类问题并不像一个空指针、数组越界这样明显好查,这时就需要刚才提到的内存模型、对象创建、线程等相关知识结合在一起来排查问题了。

正好这次借助之前的一次生产问题来聊聊如何排查和解决问题。

生产现象

首先看看问题的背景吧:

我这其实是一个定时任务,在固定的时间会开启 N 个线程并发的从 Redis 中获取数据进行运算。

业务逻辑非常简单,但应用一般涉及到多线程之后再简单的事情都要小心对待。

果不其然这次就出问题了。

现象:原本只需要执行几分钟的任务执行了几个小时都没退出。翻遍了所有的日志都没找到异常。

于是便开始定位问题之路。

定位问题

既然没办法直接从日志中发现异常,那就只能看看应用到底在干嘛了。

最常见的工具就是 JDK 自带的那一套。

这次我使用了 jstack 来查看线程的执行情况,它的作用其实就是 dump 当前的线程堆栈。

当然在 dump 之前是需要知道我应用的 pid 的,可以使用 jps -v 这样的方式列出所有的 Java 进程。

当然如果知道关键字的话直接使用 ps aux|grep java 也是可以的。

拿到 pid=1523 了之后就可以利用 jstack 1523 > 1523.log 这样的方式将 dump 文件输出到日志文件中。

如果应用简单不复杂,线程这些也比较少其实可以直接打开查看。

但复杂的应用导出来的日志文件也比较大还是建议用专业的分析工具。

我这里的日志比较少直接打开就可以了。

因为我清楚知道应用中开启的线程名称,所以直接根据线程名就可以在日志中找到相关的堆栈:

所以通常建议大家线程名字给的有意义,在排查问题时很有必要。

其实其他几个线程都和这里的堆栈类似,很明显的看出都是在做 Redis 连接。

于是我登录 Redis 查看了当前的连接数,发现已经非常高了。

这样 Redis 的响应自然也就变慢了。

接着利用 jps -v 列出了当前所以在跑的 Java 进程,果不其然有好几个应用都在查询 Redis,而且都是并发连接,问题自然就找到了。

解决办法

所以问题的主要原因是:大量的应用并发查询 Redis,导致 Redis 的性能降低。

既然找到了问题,那如何解决呢?

  • 减少同时查询 Redis 的应用,分开时段降低 Redis 的压力。
  • 将 Redis 复制几个集群,各个应用分开查询。但是这样会涉及到数据的同步等运维操作,或者由程序了进行同步也会增加复杂度。

目前我们选择的是第一个方案,效果很明显。

本地模拟

上文介绍的是线程相关问题,现在来分析下内存的问题。

以这个类为例:

https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/oom/heap/HeapOOM.java

     
1
2
3
4
5
6
7
8
9
     
public class HeapOOM {
public static void main(String[] args) {
List<String> list = new ArrayList<>(10) ;
while (true){
list.add("1") ;
}
}
}

启动参数如下:

     
1
2
3
4
     
-Xms20m
-Xmx20m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/xx/Documents

为了更快的突出内存问题将堆的最大内存固定在 20M,同时在 JVM 出现 OOM 的时候自动 dump 内存到 /Users/xx/Documents(不配路径则会生成在当前目录)。

执行之后果不其然出现了异常:

同时对应的内存 dump 文件也生成了。

内存分析

这时就需要相应的工具进行分析了,最常用的自然就是 MAT 了。

我试了一个在线工具也不错(文件大了就不适合了):

http://heaphero.io/index.jsp

上传刚才生成的内存文件之后:

因为是内存溢出,所以主要观察下大对象:

也有相应提示,这个很有可能就是内存溢出的对象,点进去之后:

看到这个堆栈其实就很明显了:

在向 ArrayList 中不停的写入数据时,会导致频繁的扩容也就是数组复制这些过程,最终达到 20M 的上限导致内存溢出了。

更多建议

上文说过,一旦使用了多线程,那就要格外小心。

以下是一些日常建议:

  • 尽量不要在线程中做大量耗时的网络操作,如查询数据库(可以的话在一开始就将数据从从 DB 中查出准备好)。
  • 尽可能的减少多线程竞争锁。可以将数据分段,各个线程分别读取。
  • 多利用 CAS+自旋 的方式更新数据,减少锁的使用。
  • 应用中加上 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp 参数,在内存溢出时至少可以拿到内存日志。
  • 线程池监控。如线程池大小、队列大小、最大线程数等数据,可提前做好预估。
  • JVM 监控,可以看到堆内存的涨幅趋势,GC 曲线等数据,也可以提前做好准备。

总结

线上问题定位需要综合技能,所以是需要一些基础技能。如线程、内存模型、Linux 等。

当然这些问题没有实操过都是纸上谈兵;如果第一次碰到线上问题,不要慌张,反而应该庆幸解决之后你又会习得一项技能。

号外

最近在总结一些 Java 相关的知识点,感兴趣的朋友可以一起维护。

地址: https://github.com/crossoverJie/Java-Interview

欢迎关注公众号一起交流:

相关 [线上 问题 思考] 推荐:

一次线上问题排查所引发的思考

- - crossoverJie's Blog
之前或多或少分享过一些 内存模型、 对象创建之类的内容,其实大部分人看完都是懵懵懂懂,也不知道这些的实际意义. 直到有一天你会碰到线上奇奇怪怪的问题,如:. 线程执行一个任务迟迟没有返回,应用假死. 接口响应缓慢,甚至请求超时. 这类问题并不像一个空指针、数组越界这样明显好查,这时就需要刚才提到的内存模型、对象创建、线程等相关知识结合在一起来排查问题了.

思考系统API设计的问题

- edware_love - C++博客-首页原创精华区
最近正好在思考系统API设计中考量的一些问题,. 我现在的理解是这样的,假设有巨大的真实内存. windows首先将高2G的内存自己占了,用作各种内核对象. 这2G内存共享给每个进程,但进程不能直接访问,只能通过windows给定的函数访问. : 然后每个进程都给他2G内存,进程如果创建自己的对象就放到自己那2G内存里面,如果要建立内核对象就放到共享的那高2G里面去.

要找出答案,或需暂停对问题的思考

- 无 - 译言-每日精品译文推荐
你是否也曾有过遗失了钥匙,急切想找到它们却无果,但当你不在去找时,反而意外地找到了它们. 不知为何,一旦你停止了寻找,无需刻意地,你的大脑便开始把零碎的线索一一拼凑起来. 事实证明创新的过程有时也是如此. 如果你困在一个问题里无法找出答案,你所能做的一件最好的事便是暂时转移注意力. 不但要移开目光,还要真的是把注意力转移开.

由 Opacity 属性引发的层叠问题思考与解决

- - 我爱水煮鱼
在最近的一个作品中,在使用 opacity 属性来实现页面整体透明的时候,发现了一个问题. 如果两个层发生了重叠,使用了 opacity 属性并且属性值小于1的层,会覆盖掉后面的层. 于是动手做了个实验,来验证 opacity 的层次. 网页中的层叠规律是这样的:如果两个层都没有定义 position 属性为 absolute 或者 relative 属性,哪个层的HTML代码放在后面,哪个层就显示在上面.

系统设计典型问题的思考

- - 四火的唠叨
最近我老婆在找工作,于是我也一起学习了一些系统设计的知识,这里总结典型的思路和题目. 首先,反复沟通和澄清系统需求. 只有把需求澄清清楚了,才可以开始思考并落到纸面上. 但是需求的沟通应该是持续和循序渐进的,问题很难从一开始就思考全面. 其次,尝试抽象一个简单的模型,从简单模型开始,思考不同的场景和约束,逐步完善.

或许你从小就一直在思考的两个算术问题

- 依云 - Matrix67: My Blog
    你是否很小就注意到了下面这两个有趣的算术现象. 这两个简单的算术谜题是否一直都困扰着你. 今天,大家终于有机会解开谜团了.     问题一: 2 加 2 等于 4 , 2 乘 2 也等于 4. 还有其它的整数对,它们的和与积也相等吗.     我们要求的就是 mn = m+n 的整数解.     由于 m 、 n 都是整数,因此 m - 1 和 n - 1 也都是整数.

线上性能问题初步排查方法

- - 并发编程网 - ifeve.com
有时候有很多问题只有在线上或者预发环境才能发现,而线上又不能Debug,所以线上问题定位就只能看日志,系统状态和Dump线程,本文只是简单的介绍一些常用的工具,帮助定位线上问题. 1: 首先使用TOP命令查看每个进程的情况,显示如下:. 我们的程序是Java应用,所以只需要关注COMMAND是Java的性能数据,COMMAND表示启动当前进程的命令,在Java进程这一行里可以看到CPU利用率是300%,不用担心,这个是当前机器所有核加在一起的CPU利用率.

线上存储服务崩溃问题分析记录

- - codedump
上周我们的存储服务在某个线上项目频繁出现崩溃,花了几天的时间来查找解决该问题. 由于问题在线上发生,较难重现,首先想到的是能不能加上更多的信息,在问题出现时提供更多的解决思路. 首先,我们的代码里,在捕获到进程退出的信号比如SIGABRT、SIGSEGV、SIGILL等信号时,会打印出主线程的堆栈,用于帮助我们发现问题.

不改一行代码定位线上性能问题

- - crossoverJie's Blog
最近时运不佳,几乎天天被线上问题骚扰. 前几天刚解决了一个 HashSet 的并发问题,周六又来了一个性能问题. 我们提供出去的一个 OpenAPI 反应时快时慢,快的时候几十毫秒,慢的时候几秒钟才响应. 由于这种也不是业务问题,不能直接定位. 所以尝试在测试环境复现,但遗憾的测试环境贼快. 中途有抱着侥幸心里让运维查看了 Nginx 里 OpenAPI 的响应时间,想把锅扔给网络.

线上服务请求慢问题排查

- - 掘金后端
收到测试的消息,项目页面打开很慢. 查看线上JVM监控平台,发现每分钟由于GC暂停的时间 30~50s. jstat -gccause pid time,发现老年代的占比一直在99%左右,并且发生full gc之后,变化很小. 然后,查看线上gc日志,发现老年代的空间在full gc 前后基本无变化.