记录一次线上故障新生代,老年代,内存溢出,IO线程,CPU全部满负载异常情况排查

记录一次线上故障新生代,老年代,内存满负荷运行,IO线程,CPU全部满负载异常情况排查:

背景:正常运行的一个某个业务系统,并发量比较大,相当于10万用户每5秒访问一次的数据量。因为某次发版之后,运行一段时间之后,通过监控,发现新生代内存,老年代内存都是99%的使用情况,单台实例服务IO线程达到600多,cpu使用率达到90%。GC垃圾回收时长大概比平常高出10倍以上


排查思路:

1.出现这样的情况,首先想到的是代码问题,不是环境配置,和服务器本身问题。但还是先检查了服务器和环境,最开始想到的是代码有for循环,以及大对象读取创建。
例如

for (int i=0;i<100000000;i++){
            // 创建对象
            Object o = new Object();
            // 复杂逻辑处理
            System.out.println(o.toString());
        }

或者文件的加载读取

Workbook wb = null;
InputStream is =  getClass().getClassLoader().getResourceAsStream("2.xlsx");
wb = new XSSFWorkbook(is);

2.经过第一轮排查,并没有发现服务器有问题,然后查看dump记录(正常情况,通过dump即可发现问题),但是由于出现问题之后,系统比较重要,那边紧急回滚了,导致dump堆栈日志重新加载,删掉了记录。导致无法从这个地方打开入口

3.只能先查看配置以及代码,然后检查配置文件,比如.properties文件,和。yml文件以及其它加载文件

3.同样没有检查到问题。然后排查代码,并没有明细的for循环和大对象创建。这个时候由于业务非常复杂,我们采取分段排查方法。我们的流程当时是:接受数据-》解析数据-》组装数据-》处理数据-》发送数据

我们把:接受数据-》解析数据-》组装数据-》 分为第一段。分段发送这一段代码。查看和第三方对接是否有异常。

(由于线下环境,测试和预生产都模拟不出来上述情况)发了其中一台生产实例,然后通过ip代理指向我们特定的实例一些流量。检查没问题。

4.然后把:处理数据 分为第二段,上线同样的操作。出现了问题。关闭。再次认真 check 这段逻辑。终于在一个方法上发现了一个问题点:
本地缓存代码

// 从redis拿到数据,缓存到本地ConcurrentHashMap
public static ConcurrentHashMap<String,xxxDto> map =  new ConcurrentHashMap();
    public  xxxDto getxxxData(String terminalId){
        if (map.isEmpty()) {
            // 从redis拿数据的方法
            List<xxxDto> list = dataCov();
            if (list == null) {
                return null;
            }
          for (int i =0 ;i < list.size();i++){
              xxxDto xxxDto = list.get(i);
              map.put(xxxDto.getTerminalId(),xxxDto) ;
          }
        }
        if(map.isEmpty()){
            return null;
        }
        return map.get(terminalId);
    }

看下这段代码有没有问题,当几万个用户,平均每5秒的频率访问。会不会出问题?

首先没有加锁。其次当并发量在几万的频率的话,并没有提前预加载redis缓存数据到本地缓存。 而是每次使用到,在请求redis读取,放入本地缓存。导致大批量请求来的时候。同时出发条件加载数据。在没有加载完时,还有源源不断请求过来。导致其它请求处于等待状态。越来越多线程过来都等待。堆积的对象越来越多。最终呈现的是新生代,老年代,内存满负荷运行,IO线程,CPU全部满负载异常情况。

上一篇:mit6.824lab1


下一篇:Java:Collection集合(二)Map