问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

一次CPU占用过高问题的排查与优化实战

创作时间:
作者:
@小白创作中心

一次CPU占用过高问题的排查与优化实战

引用
CSDN
1.
https://blog.csdn.net/V_zxw/article/details/139657018

本文记录了一次CPU占用过高问题的排查和优化过程。通过使用arthas和JProfile等工具,定位到LinkedBlockingQueue的poll方法和List.contains方法是主要的性能瓶颈。通过调整批处理频率、优化数据结构以及使用本地缓存等措施,成功降低了CPU使用率。

背景

在一次线上运行过程中,监控系统不断预警CPU占用达到100%。为了解决这一问题,开始着手进行排查。

排查过程

arthas观察

首先登录服务器,查看当前服务的日志输出情况。日志显示一切正常,除了偶尔有几个异常外没有明显的问题。使用arthas进入应用服务的监控界面后,发现前几个线程的CPU占用率都接近了30%。通过线程名称定位到具体的功能模块,发现这些线程主要负责消费MQ数据并进行计算和数据库写入,每秒的MQ消费量很大,达到几十万条。

profile记录

为了进一步分析问题,我们在本地启动项目,通过IDEA自带的JProfile对项目进行分析。运行5分钟后,生成了jfr运行效果图。通过图表可以看到每个线程的CPU占用情况,点进具体的线程后,发现LinkedBlockingQueue的poll方法占用比例最高,高达7.5%。

应用的处理逻辑是在线程内部启动循环去持续poll数据,目的是为了批量攒数据,然后在等待时间内一次性处理,以减少对数据库的访问次数。具体代码如下:

while (!blockingQueue.isEmpty() && System.currentTimeMillis() - current < 10000) {
    T take1 = blockingQueue.poll();
    if (take1 != null) {
        data.put(take1.getId(), take1);
    }
}

针对这一问题,我们将批量处理的频率从10秒调整为5秒:

while (!blockingQueue.isEmpty() && System.currentTimeMillis() - current < 5000) {
    T take1 = blockingQueue.poll();
    if (take1 != null) {
        data.put(take1.getId(), take1);
    }
}

解决完第一个问题点后,我们继续分析数据处理部分。通过IDEA自带的分析工具,发现处理数据的代码块消耗严重,特别是在List.contains方法的调用上。原因是这个List比较大,大概有几万条数据,频繁的循环调用和判断导致性能瓶颈。

通过排查发现,问题出在以下代码片段:

foreach(e->{
   if(List.contains()){}
})

将List替换为Set后,CPU的使用率显著降低。这是因为Set的查询复杂度在最好情况下为O(1),而List的contains方法查询复杂度为O(n)。

总结

List Or Set

  • List的contains方法查询复杂度为O(n)
  • Set的查询复杂度最好的情况下为O(1),最坏的时间复杂度可能接近 O(n)

在需要判断一个元素是否在列表中,且数据量大且调用频繁的情况下,建议使用Set结构,以提高查询效率。

LinkedBlockingQueue

使用LinkedBlockingQueue进行异步操作时,常见的批处理做法是在内部while循环获取一批数据。但是这种方式下poll方法执行很快,也会占用很多CPU使用时间。建议将等待时间尽量调短,以便减少CPU的消耗。例如:

T take = blockingQueue.take();
while ( data.size() < 5000 && System.currentTimeMillis() - current < 5000) {
    T take1 = blockingQueue.poll();
}

请求量高的情况下尽量使用本地缓存减少网络IO

在高并发场景下,频繁调用远程服务(如Redis)获取数据会导致CPU使用率升高。例如,应用每分钟消费的MQ数量在3.40W左右,处理时涉及到部分计算会去从Redis取一些不变的数据。在这种情况下,网络IO占用的CPU也会很高。因此,建议对于不变的数据尽量使用本地缓存存储,并设置一个过期时间定期刷新。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号