您的位置:首页 > 产品设计 > UI/UE

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

2016-06-08 10:53 621 查看
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爆表的问题算是解决了,至于日志对象没有保存到数据库是为什么,那就是另外的问题了,也跟线程相关,那么且听下回分解。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  性能 cpu