日常Bug排查-系统失去响应-Redis使用不当

存储 存储软件 Redis
开发反应线上系统出现失去响应的现象,收到业务告警以及频繁MarkAndSweep(Full GC)告警。于是找到笔者进行排查。

[[400658]]

前言

日常Bug排查系列都是一些简单Bug排查,笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材^_^。

Bug现场

开发反应线上系统出现失去响应的现象,收到业务告警以及频繁MarkAndSweep(Full GC)告警。于是找到笔者进行排查。

看基础监控

首先呢,当然是看我们的监控了,找到对应失去响应的系统的ip,看下我们的基础监控。

机器内存持续上升。因为我们是java系统,堆的大小一开始已经设置了最大值。

  1. --XX:Xms2g -Xmx2g 

所以看上去像堆外内存泄露。而FullGC告警只是堆外内存后一些关联堆内对象触发。

看应用监控

第二步,当然就是观察我们的应用监控,这边笔者用的是CAT。观察Cat中对应应用的情况,很容易发现,其ActiveThread呈现不正常的现象,竟然达到了5000+多个,同时和内存上升曲线保持一致。

jstack

java应用中遇到线程数过多的现象,首先我们考虑的是jstack,jstack出来对应的文件后。我们less一下,发现很多线程卡在下面的代码栈上。

  1. "Thread-1234 
  2.     java.lang.Thread.State: WAITING (parking) 
  3.         at sun.misc.Unsafe.park 
  4.         ...... 
  5.         at org.apache.commons.pool2.impl.LinkedBlockingQueue.takeFirst 
  6.         ...... 
  7.         at redis.clients.util.Pool.getResource 

很明显的,这个代码栈指的是没有获取连接,从而卡住。至于为什么卡这么长时间而不释放,肯定是由于没设置超时时间。那么是否大部分线程都卡在这里呢,这里我们做一下统计。

  1. cat jstack.txt | grep 'prio=' | wc -l  
  2. ======> 5648 
  3. cat jstack.txt | grep 'redis.clients.util.Pool.getResource'  
  4. ======> 5242 

可以看到,一共5648个线程,有5242,也就是92%的线程卡在Redis getResource中。

看下redis情况

  1. netstat -anp | grep 6379  
  2. tcp 0 0 1.2.3.4:111 3.4.5.6:6379 ESTABLISHED 
  3. ...... 

一共5个,而且连接状态为ESTABLISHED,正常。由此可见他们配置的最大连接数是5(因为别的线程正在等待获取Redis资源)。

Redis连接泄露

那么很自然的想到,Redis连接泄露了,即应用获得Redis连接后没有还回去。这种泄露有下面几种可能:

情况1:

情况2:

情况3:

调用Redis卡住,由于其它机器是好的,故排除这种情况。

如何区分

我们做个简单的推理:

如果是情况1,那么这个RedisConn肯定可以通过内存可达性分析和Thread关联上,而且这个关联关系肯定会关联到某个业务操作实体(例如code stack or 业务bean)。那么我们只要观察其在堆内的关联路线是否和业务相关即可,如果没有任何关联,那么基本断定是情况2了。

可达性分析

我们可以通过jmap dump出应用内存,然后通过MAT(Memory Analysis Tool)来进行可达性分析。

首先找到RedisConn

将dump文件在MAT中打开,然后运行OQL:

  1. select * from redis.clients.jedis.Jedis (RedisConn的实体类) 

搜索到一堆Jedis类,然后我们执行

  1. Path To GCRoots->with all references 

可以看到如下结果:

  1. redis.clients.jedis.Jedis 
  2.     |->object  
  3.         |->item 
  4.             |->first 
  5.                 |->... 
  6.                     |->java.util.TimerThread 
  7.                 |->internalPool 

由此可见,我们的连接仅仅被TimerThread和internalPool(Jedis本身的连接池)持有。所以我们可以判断出大概率是情况2,即忘了归还连接。翻看业务代码:

  1. 伪代码 
  2. void lock(){ 
  3.     conn = jedis.getResource() 
  4.     conn.setNx() 
  5.     // 结束,此处应该有finally{returnResource()}或者采用RedisTemplate 

最后就是很简单的,业务开发在执行setNx操作后,忘了将连接还回去。导致连接泄露。

如果是情况1如何定位卡住的代码

到此为止,这个问题是解决了。但是如果是情况1的话,我们又该如何分析下去呢?很简单,我们如果找到了jedis被哪个业务线程拥有,直接从heap dump找到其线程号,然后取Jstack中搜索即可知道其卡住的代码栈。

  1. jmap: 
  2. redis.clients.jedis.Jedis 
  3.     |->Thread-123 
  4.  
  5. jstack: 
  6.  
  7. Thread-123 prio=... 
  8.     at xxx.xxx.xxx.blocked 

总结

这是一个很简单的问题,知道套路之后排查起来完全不费事。虽然最后排查出来是个很低级的代码,但是这种分析方法值得借鉴。

责任编辑:武晓燕 来源: 解Bug之路
相关推荐

2020-10-22 07:09:19

TCP网络协议

2021-06-10 06:59:34

Redis应用API

2021-09-11 19:00:54

Intro元素MemoryCache

2019-10-10 15:40:17

redisbug数据库

2022-10-25 18:00:00

Redis事务生产事故

2009-12-17 14:53:52

VS2008程序

2021-08-26 14:26:25

Java代码集合

2021-07-11 09:34:45

ArrayListLinkedList

2022-06-21 11:24:05

多线程运维

2024-02-04 08:26:38

线程池参数内存

2024-06-28 10:01:04

2011-08-18 13:49:32

笔记本技巧

2021-06-04 11:33:50

消息技巧排查

2024-09-05 08:07:55

2024-05-13 10:21:43

Bug排查TCP

2021-06-07 09:37:05

异常Bug排查

2010-01-06 10:56:47

华为交换机使用

2021-05-19 14:03:48

磁盘故障

2020-02-06 11:30:08

代码JavaScript&&

2021-07-30 20:59:21

MySQL内存.参数
点赞
收藏

51CTO技术栈公众号