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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: 之前或多或少分享过一些内存模型、对象创建之类的内容,其实大部分人看完都是懵懵懂懂,也不知道这些的实际意义。

前言


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


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


  • 线程执行一个任务迟迟没有返回,应用假死。


  • 接口响应缓慢,甚至请求超时。


  • 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 复制几个集群,各个应用分开查询。但是这样会涉及到数据的同步等运维操作,或者由程序了进行同步也会增加复杂度。


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


本地模拟


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


以这个类为例:


github.com/crossoverJi…


public class HeapOOM {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(10) ;
        while (true){
            list.add("1") ;
        }
    }
}


启动参数如下:


-Xms20m
-Xmx20m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/xx/Documents


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


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



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


内存分析


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


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


heaphero.io/index.jsp


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



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



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



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


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


更多建议


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


以下是一些日常建议:


  • 尽量不要在线程中做大量耗时的网络操作,如查询数据库(可以的话在一开始就将数据从从 DB 中查出准备好)。


  • 尽可能的减少多线程竞争锁。可以将数据分段,各个线程分别读取。


  • 多利用 CAS+自旋 的方式更新数据,减少锁的使用。


  • 应用中加上 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp 参数,在内存溢出时至少可以拿到内存日志。


  • 线程池监控。如线程池大小、队列大小、最大线程数等数据,可提前做好预估。


  • JVM 监控,可以看到堆内存的涨幅趋势,GC 曲线等数据,也可以提前做好准备。


总结


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


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


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
SQL 监控 网络协议
线上故障如何快速排查?来看这套技巧大全
有哪些常见的线上故障?如何快速定位问题?本文详细总结工作中的经验,从服务器、Java应用、数据库、Redis、网络和业务六个层面分享线上故障排查的思路和技巧。较长,同学们可收藏后再看。
线上故障如何快速排查?来看这套技巧大全
|
7月前
|
SQL
线上问题排查日志实战
线上问题排查日志实战
55 1
|
JavaScript
开发遇到的问题排查
开发遇到的问题排查
|
7月前
|
SQL 运维 监控
如何排查线上问题的?
在当今的互联网时代,线上问题对企业的业务连续性和用户体验产生的影响越来越大。无论是网站崩溃、应用性能下降,还是服务中断,这些问题都可能对企业的声誉和用户满意度造成严重影响。因此,快速、准确地排查并解决线上问题变得至关重要。本文将介绍一些高效的线上问题排查方法,帮助您在面对线上问题时,迅速定位并解决问题。我们将在接下来的内容中详细讨论如何利用日志分析、监控系统、代码审查等手段,以及如何制定有效的应急预案。通过这些策略的实施,您将能够提高线上问题的解决速度,减少对业务的影响,并提高用户满意度。
174 2
|
7月前
|
测试技术
线上环境漏测问题根因分析
线上环境漏测问题根因分析
113 0
|
运维 监控 前端开发
记一次线上 bug 的排查分析过程及总结
记一次线上 bug 的排查分析过程及总结
记一次线上 bug 的排查分析过程及总结
|
运维 PHP Perl
总结一些线上问题排查的命令,可能用得到!
开发运维,统计所遇到的运维问提。运维问提排查,以下场景,你可能遇到?
183 0
总结一些线上问题排查的命令,可能用得到!
|
Kubernetes Docker 容器
问题排查
问题排查
102 0
|
前端开发 JavaScript 程序员
如何追踪线上错误
如何追踪线上错误
182 0
如何追踪线上错误
|
运维 监控 NoSQL
排查线上问题的9种方式
排查线上问题的9种方式
 排查线上问题的9种方式