当前位置: 首页 > 科技观察

日常查错-系统无响应-Redis使用不当

时间:2023-03-18 16:14:36 科技观察

前言日常查错系列是一些简单的查错。笔者将在这里介绍一些简单的查错技巧,顺便积累素材^_^。Bug现场开发响应线上系统无响应,收到业务告警,MarkAndSweep(FullGC)告警频繁。于是找到笔者进行调查。看基础监控首先当然是看我们的监控,找到失去响应的系统对应的ip,看我们的基础监控。机器内存继续上升。因为我们是java系统,堆的大小一开始就设置了一个最大值。--XX:Xms2g-Xmx2g所以看起来像是堆外内存泄漏。FullGC警报仅由堆外内存之后的堆中的某些关联对象触发。看应用监控的第二步,当然是观察我们的应用监控,这里笔者使用的是CAT。观察Cat中对应应用的情况,很容易发现其ActiveThread异常,达到了5000+以上,与内存上升曲线一致。遇到jstackjava应用中线程过多,首先我们考虑jstack,jstack出来后对应的文件。稍等片刻,发现很多线程都卡在了下面的代码栈中。“Thread-1234java.lang.Thread.State:等待(停车)atsun.misc.Unsafe.park...atorg.apache.commons.pool2.impl.LinkedBlockingQueue.takeFirst...atredis.clients.util.Pool.getResource很明显这段代码栈是指没有得到连接,所以卡住了,这里我们做一些统计catjstack.txt|grep'prio='|wc-l=======>5648catjstack.txt|grep'redis.clients.util.Pool.getResource'=====>5242可以看到总共有5648个线程,5242,也就是92%的线程卡在了Redis的getResource中。查看redis情况netstat-anp|grep6379tcp001.2.3.4:1113.4.5.6:6379ESTABLISHED...一共5个,连接状态为ESTABLISHED,正常。可以看出他们配置的最大连接数是5(因为其他线程在等待获取Redis资源)。Redisconnectionleak那么自然而然的就会想到是Redis连接泄露了,也就是应用获取到Redis连接后没有返回。这种泄漏有几种可能:情况一:情况二:情况三:Redis的调用卡住了。由于其他机器都很好,所以排除这种情况。如何区分我们做一个简单的推理:如果是case1,那么通过内存可达性分析,这个RedisConn肯定可以和Thread关联起来,而且这个关联关系一定是关联到某个业务操作实体(比如代码栈或者业务豆)。那么我们只需要观察其在堆中的关联路由是否与业务相关即可。如果没有关联,那么基本可以断定是case2。可达性分析我们可以通过jmapdump应用内存,然后通过MAT(MemoryAnalysisTool)进行可达性分析。首先找到RedisConn,在MAT中打开dump文件,然后运行OQL:select*fromredis.clients.jedis.Jedis(RedisConn的实体类)搜索一堆Jedis类,然后我们执行PathToGCRoots->withallreferences看看结果如下:redis.clients.jedis.Jedis|->object|->item|->first|->...|->java.util.TimerThread|->internalPool可以看出我们的连接只有TimerThread和internalPool(Jedis本身的连接池)持有。所以我们可以判断大概率是情况2,即忘记归还连接。看业务代码:伪代码voidlock(){conn=jedis.getResource()conn.setNx()//结束,应该有finally{returnResource()}或者使用RedisTemplate}最后,很简单,业务开发在执行了setNx操作后,忘记返回连接了。导致连接泄漏。如果是case1怎么定位卡住的代码至此,这个问题就解决了。但是如果是case1,我们应该怎么分析呢?这很简单。如果我们找出哪个业务线程拥有jedis,我们可以直接从heapdump中找到它的线程号,然后在Jstack中搜索就知道卡在哪里了。代码栈。jmap:redis.clients.jedis.Jedis|->Thread-123jstack:Thread-123prio=...atxxx.xxx.xxx.blocked总结这是一个很简单的问题,知道套路之后查起来也不麻烦。虽然最后查出来是很底层的代码,但是这种分析方法还是值得学习的。