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

CPU 100% 优化排查实战

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

CPU 100% 优化排查实战

引用
CSDN
1.
https://blog.csdn.net/gaosw0521/article/details/144935416

1 问题背景

某服务器负载异常升高,经初步排查发现,服务器上仅运行着一个Java应用程序。技术人员随即展开了详细的排查和优化工作。

2 排查步骤

2.1 获取进程信息

首先使用ps命令获取应用的PID:

ps -ef | grep java

2.2 查看线程 CPU 使用情况

使用top命令查看该进程的线程信息,并按CPU使用率排序(输入大写P):

top -Hp <pid>

发现某些线程的CPU使用率高达99.9%。

2.3 导出线程栈信息

为了进一步分析,使用jstack命令将线程栈信息导出到日志文件中:

jstack <pid> > pid.log

2.4 分析线程栈

在99.9% CPU使用率的线程中,随机选择一个线程(例如pid=194283),将其转换为16进制(2f6eb),并在线程快照中查找对应的线程信息。发现这些线程都与Disruptor队列相关,且都在执行java.lang.Thread.yield方法。

2.5 使用分析工具

为了更直观地查看线程状态,将线程快照信息上传到fastthread.io进行分析。分析结果显示,几乎所有消耗CPU的线程都与Disruptor队列相关,且都在执行yield方法。

2.6 初步判断

初步判断,大量线程执行yield方法后,互相竞争导致CPU使用率增高。通过对堆栈的分析,发现确实与Disruptor有关。

3 Disruptor 使用方式

3.1 引入依赖

pom.xml文件中引入Disruptor的依赖:

<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.2</version>
</dependency>

3.2 定义事件

定义事件LongEvent

public static class LongEvent {
    private long value;
    public void set(long value) {
        this.value = value;
    }
    @Override
    public String toString() {
        return "LongEvent{value=" + value + '}';
    }
}

3.3 定义事件工厂

定义事件工厂LongEventFactory

public static class LongEventFactory implements EventFactory<LongEvent> {
    @Override
    public LongEvent newInstance() {
        return new LongEvent();
    }
}

3.4 定义事件处理器

定义事件处理器LongEventHandler

public static class LongEventHandler implements EventHandler<LongEvent> {
    @Override
    public void onEvent(LongEvent event, long sequence, boolean endOfBatch) {
        System.out.println("Event: " + event);
    }
}

3.5 定义事件发布者

定义事件发布者:

public static void main(String[] args) throws InterruptedException {
    // 指定 Ring Buffer 的大小
    int bufferSize = 1024;
    // 构建 Disruptor
    Disruptor<LongEvent> disruptor = new Disruptor<>(
            new LongEventFactory(),
            bufferSize,
            Executors.defaultThreadFactory());
    // 连接事件处理器
    disruptor.handleEventsWith(new LongEventHandler());
    // 启动 Disruptor
    disruptor.start();
    // 获取 Ring Buffer
    RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
    // 生产事件
    ByteBuffer bb = ByteBuffer.allocate(8);
    for (long l = 0; l < 100; l++) {
        bb.putLong(0, l);
        ringBuffer.publishEvent((event, sequence, buffer) -> event.set(buffer.getLong(0)), bb);
        Thread.sleep(1000);
    }
    // 关闭 Disruptor
    disruptor.shutdown();
}

简单解释下:

  • LongEvent:这是要通过Disruptor传递的数据或事件。
  • LongEventFactory:用于创建事件对象的工厂类。
  • LongEventHandler:事件处理器,定义了如何处理事件。
  • Disruptor构建:创建了一个Disruptor实例,指定了事件工厂、缓冲区大小和线程工厂。
  • 事件发布:示例中演示了如何发布事件到Ring Buffer。

运行结果如下:

4 问题定位与优化

4.1 问题定位

通过代码审查,发现每个业务场景内部都使用了2个Disruptor队列来解耦。假设有7个业务,则创建了14个Disruptor队列,每个队列有一个消费者,总共14个消费者。配置的消费等待策略为YieldingWaitStrategy,这种策略会执行yield来让出CPU。

4.2 本地模拟

为了验证问题,在本地创建了15个Disruptor队列,并结合监控观察CPU使用情况。发现CPU使用率确实很高,且线程状态与生产环境一致。

注意看代码YieldingWaitStrategy:

以及事件处理器:

创建了15个Disruptor队列,同时每个队列都用线程池来往Disruptor队列里面发送100W条数据。消费程序仅仅只是打印一下。

跑了一段时间,发现CPU使用率确实很高。

同时dump线程发现和生产环境中的现象也是一致的:消费线程都处于RUNNABLE状态,同时都在执行yield。

4.3 调整等待策略

通过查询Disruptor官方文档,发现YieldingWaitStrategy适用于消费线程数量小于CPU核心数的情况。而当前场景中,消费线程数远超过CPU核心数。因此,将等待策略调整为BlockingWaitStrategy

,发现CPU使用率明显降低。

运行后的结果如下:

dump线程后,发现大部分线程都处于waiting状态。

4.4 进一步优化

将Disruptor队列调整为1个,并保持YieldingWaitStrategy策略,发现CPU使用率保持平稳。

5 优化方案

  1. 调整等待策略:将等待策略从YieldingWaitStrategy调整为BlockingWaitStrategy,以降低CPU使用率。
  2. 应用拆分:将现有业务拆分为多个应用,每个应用处理一种业务类型,分别部署,以实现隔离。
  3. 线程池优化:调整线程池配置,减少线程数量,避免空闲线程占用资源。

6 总结

通过本次排查,技术人员发现CPU使用率过高的问题与Disruptor的等待策略和线程数量有关。通过调整等待策略和应用拆分,可以有效降低CPU使用率。希望本次排查思路能为大家提供一些启发。

7 思维导图

8 参考链接

一次生产环境中CPU占用100%排查优化实践

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