当前位置: 首页 > article >正文

CPU 100% 优化排查实战

1 问题背景

收到运维同学的报警,某些服务器负载非常高。经过初步排查,发现服务器上运行的只有我们的 Java 应用程序。于是,我们开始了一系列的排查和优化工作。

2 排查步骤

2.1 获取进程信息

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

ps -ef | grep java

2.2 查看线程 CPU 使用情况

使用 top -Hp pid 命令查看该进程的线程信息,并按 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% 排查优化实践


http://www.kler.cn/a/468636.html

相关文章:

  • Nginx——入门介绍、安装与核心配置文件结构(一/五)
  • 【C语言程序设计——选择结构程序设计】预测你的身高(头歌实践教学平台习题)【合集】
  • Windows 11 上通过 WSL (Windows Subsystem for Linux) 安装 MySQL 8
  • 谈一谈对事件循环的理解
  • 网络协议安全的攻击手法
  • Cyber Security 101-Web Hacking-Burp Suite: The Basics(Burp Suite:基础知识)
  • Maven的依赖管理
  • 深入理解卷积神经网络(CNN):图像识别的强大工具
  • R语言安装教程与常见问题
  • 第P4周-Pytorch实现猴痘病识别
  • leetcode(hot100)4
  • C++编程等级认证学习计划
  • 一种可复用的AI提效方案:AI点灯
  • Springboot项目部署以及jar包属性配置
  • 分类、聚类与回归的评价指标
  • 【NLP高频面题 - 分布式训练篇】ZeRO主要为了解决什么问题?
  • CSS——10.类选择器
  • 【Go学习】-01-6-数据库泛型新特性
  • 如何处理外在关系以及内在关系,思维冲突和纠结
  • 挑战20天刷完leecode100
  • C语言程序设计(第5版)习题解答-第4章
  • stm32HAL库使LED闪烁
  • ArcGIS中怎么把数据提取到指定范围(裁剪、掩膜提取)
  • RabbitMQ-基本使用
  • ChatGPT 主流模型GPT-4/GPT-4o mini的参数规模是多大?
  • 学习扩散模型的完整指南(前提知识、DDPM、稳定扩散、DreamBooth等)