生产项目中queue同步问题导致项目部署后CPU爆表问题解决

来源:互联网 发布:g代码编程 编辑:程序博客网 时间:2024/05/07 22:55

1.问题描述
最近参与到一个新项目的开发当中,项目的大部分功能已经由另外一名同事实现,这名同事给我反映了一个问题,说每次部署这个项目成功之后CPU立马飙到将近百分之百,一直没发现问题在哪里。

这里写图片描述

2.解决过程
听了同事的描述之后,心想肯定是程序性能问题,由于之前一直没碰到过这类问题,怀着浓厚的好奇心想一探究竟,解决这类问题本省也是成长,说干就干。
我首先想到的解决这类问题是使用性能分析工具——Java VisualVM,JDK自带的可视化工具。

这里写图片描述

通过VisualVM可以看到,除了CPU爆表之外其他都比较正常。
再看看线程运行情况,按运行时间排序,发现占用CPU最多的线程如绿色部分,其中有RMI相关线程、I/O线程。

这里写图片描述

看到这个排名,首先怀疑是RMI远程调用相关问题,由于项目中使用了Dubbo等框架,再加上性能分析经验不足,一直以为问题的产生跟dubbo相关,然后就去项目中去掉dubbo相关的功能(虽然有点二,当时觉得值得试一试),花了不少功夫终于去掉dubbo可以重新部署了。
重新部署项目后,发现CPU突然又飙升上去了。显然与dubbo关系不大,接下来的怀疑对象就是OpenapiMonitorAsyncWriteLogThread这个线程,由于并不是我写的代码,所以并不了解这个线程的作用。我们接着生成线程的Dump文件:

这里写图片描述

接着看Dump文件,找到OpenapiMonitorAsyncWriteLogThread这个线程的相关信息,我们就可以跟踪到相应的代码了

这里写图片描述

到这里我们只是怀疑OpenapiMonitorAsyncWriteLogThread这个线程有问题,并没有确定,那么我们再看看抽样器对CPU抽样(如下图),发现这个线程中的pollOne()方法占用CPU很多,所以我们更加怀疑这个线程,所以还是值得去仔细研究一下这个线程中的代码。

这里写图片描述

看了代码之后发现,这个线程实现的功能就是在项目部署后创建一个线程,这个线程不断的向数据库中保存访问请求日志,这些日志对象首先放到一个队列当中,然后线程不停的从queue当中取出对象:

/**     * 获取待插入记录list     * @Title: pollBatch     * @param batchSize     * @return     * @throws ParseException     * @throws InterruptedException      */    private List<OauthLog> pollBatch(int batchSize) throws InterruptedException {        List<OauthLog> retList = Lists.newLinkedList();        int count = 1;        while(count < batchSize){            OauthLog data = pollOne();            if(data != null){                retList.add(data);            }            count++;        }        return retList;    }    /**     * 获取待插入记录     * @Title: pollOne     * @return     * @throws InterruptedException      * @throws ParseException     */    private OauthLog pollOne() throws InterruptedException {        return queue.poll();}

先前我们怀疑的pollOne()方法很简单,就是一个调用queue.poll()(这个queue是用的LinkedBlockingQueue)。我们去看看这个poll()方法的源码有什么特别之处呢:

这里写图片描述

经过仔细琢磨,终于发现问题。

int count = 1;while(count < batchSize){    OauthLog data = pollOne();    if(data != null){        retList.add(data);    }    count++;}

这个循环会一直调用pollOne()这个方法,也就是上图中的poll()方法,然而当queue为空的时候,那么poll()方法就会直接返回,继续回到while循环当中,如果while循环能够终止那么也不会有问题,关键是while循环所在方法一直在运行,如下图:

这里写图片描述

从而While循环所在方法会一直运行,所以问题就出在这里,到此为止我们已经找到了问题所在,就是从queue中获取日志对象的方法不管queue是否为空都会一直运行,就导致了CPU被这个线程一直消耗。

3.问题解决
我们上面已经知道了问题之所在,现在要做的就是解决问题。既然poll()方法在队列为空的情况下还会一直继续运行,那么我们就不应该用此方法来获取队列中的对象,而是使用take()方法。

这里写图片描述

显然take()方法在队列为空的情况下就会阻塞,从而避免了线程一直占用cpu的问题。

OK,将poll()方法换成take()方法,CPU瞬间正常!
大功告成?NO!然而却出现了另外的异常现象,获取到的日志对象并没有保存到数据库。这是什么原因呢。。。不管如何CPU爆表的问题算是解决了,至于日志对象没有保存到数据库是为什么,那就是另外的问题了,也跟线程相关,那么且听下回分解。

0 0
原创粉丝点击