网易二面:CPU狂飙900%,该怎么处理?

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

来源:blog.csdn.net/weixin_45334346/article/details/136923606

首先,说明一下问题:CPU 飙升 200% 以上是生产容易发生的场景

场景: 1:MySQL 进程飙升 900%

大家在使用 MySQL 过程,想必都有遇到过 CPU 突然过高,或者达到 200% 以上的情况。

数据库执行查询或数据修改操作时,系统需要消耗大量的 CPU 资源维护从存储系统、内存数据中的一致性。

并发量大并且大量 SQL 性能低的情况下,比如字段是没有建立索引,则会导致快速 CPU 飙升,如果还开启了慢日志记录,会导致性能更加恶化。生产上有 MYSQL 飙升 900% 的恶劣情况。

场景 2:Java 进程飙升 900%

一般来说 Java 进程不做大量 CPU 运算,正常情况下,CPU 应该在 100~200% 之间,

但是,一旦高并发场景,要么走到了死循环,要么就是在做大量的 GC,  容易出现这种 CPU 飙升的情况,CPU 飙升 900%,是完全有可能的。

其他场景:其他的类似进程飙升 900% 的场景

比如 Redis、Nginx 等等。

陈某提示:大家介绍场景的时候,就说自己主要涉及了两个场景, Java 进程飙升 900%、MySQL 进程飙升 900% 两种场景,其实,这两个场景就足够讲半天了, 其他的,使用规避技巧规避一下就行。

场景一:MySQL 进程 CPU 飙升到 900%,怎么处理?

定位过程:

  • 使用 top 命令观察,确定是 mysqld 导致还是其他原因。

  • 如果是 mysqld 导致的,show processlist,查看 session 情况,确定是不是有消耗资源的 sql 在运行。

  • 找出消耗高的 sql,看看执行计划是否准确, index 是否缺失,或者实在是数据量太大造成。

处理过程:

  • kill 掉这些线程 (同时观察 cpu 使用率是否下降), 一般来说,肯定要 kill 掉这些线程(同时观察 cpu 使用率是否下降),等进行相应的调整(比如说加索引、改 sql、改内存参数) 之后,再重新跑这些 SQL。

  • 进行相应的调整 (比如说加索引、改 sql、改内存参数)

    index 是否缺失,如果是,则  建立索引。也有可能是每个 sql 消耗资源并不多,但是突然之间,有大量的 session 连进来导致 cpu 飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等;

  • 优化的过程,往往不是一步完成的,而是一步一步,执行一项优化措辞,再观察,再优化。

场景 1 的真实案例:MySQL 数据库优化的真实案例

陈某提示:以下案例,来自互联网。大家参考一下,准备一个自己的案例。

本问题亲身经历过。

之前开发同事编写的 SQL 语句,就导致过线上 CPU 过高,MySQL 的 CPU 使用率达到 900%+,通过优化最后降低到 70%~80%。下面说说个人在这个过程中的排查思路。

首先,我们要对问题定位而不是盲目的开启什么 慢日志,在并发量大并且大量 SQL 性能低的情况下,开启慢日志无意是将 MySQL 推向崩溃的边缘。

当时遇到这个情况,分析了当前的数据量、索引情况、缓存使用情况。目测数据量不大,也就几百万条而已。接下来就去定位索引、缓存问题。

经过询问,发现很多查询都是走 MySQL,没有用到缓存。

既然没有用到缓存,则是大量请求全部查询 MySQL 导致。通过下面的命令查看:

show processlist;

发现类似很多相同的 SQL 语句,一直处于 query 状态中。

select id form user where user_code = 'xxxxx';

初步分析可能是 user_code 字段没有索引导致。接着查询 user 表的索引情况:

show index form user;

发现这个字段是没有建立索引。增加索引之后,该条 SQL 查询能够正常执行。

3、没隔一会,又发生大量的请求超时问题。接着进行分析,发现是开启了 慢日志查询。大量的 SQL 查询语句超过慢日志设置的阀值,于是将慢日志关闭之后,速度瞬间提升。CPU 的使用率基本保持在 300% 左右。但还不是理想状态。

4、紧接着将部分实时查询数据的 SQL 语句,都通过缓存 (redis) 读写实现。观察一段时间后,基本维持在了 70%~80%。

总结:其实本次事故的解决很简单,就是添加索引与缓存结合使用。

  1. 不推荐在这种 CPU 使用过高的情况下进行慢日志的开启。因为大量的请求,如果真是慢日志问题会发生日志磁盘写入,性能贼低。

  2. 直接通过 MySQL show processlist 命令查看,基本能清晰的定位出部分查询问题严重的 SQL 语句,在针对该 SQL 语句进行分析。一般可能就是索引、锁、查询大量字段、大表等问题导致。

  3. 再则一定要使用缓存系统,降低对 MySQL 的查询频次。

  4. 对于内存调优,也是一种解决方案。

专属福利
👉点击领取:最全Java资料合集

场景 2 展开:Java 进程 CPU 飙升到 900%,怎么处理?

定位过程:

CPU 飙升问题定位的一般步骤是:

  • 首先通过 top 指令查看当前占用 CPU 较高的进程 PID;

  • 查看当前进程消耗资源的线程 PID:top -Hp PID

  • 通过 print 命令将线程 PID 转为 16 进制,根据该 16 进制值去打印的堆栈日志内查询,查看该线程所驻留的方法位置。

  • 通过 jstack 命令,查看栈信息,定位到线程对应的具体代码。

  • 分析代码解决问题。

处理过程:

  1. 如果是空循环,或者空自旋。

处理方式:可以使用 Thread.sleep 或者加锁,让线程适当的阻塞。

  1. 在循环的代码逻辑中,创建大量的新对象导致频繁 GC。比如,从 mysql 查出了大量的数据,比如 100W 以上等等。

处理方式:可以减少对象的创建数量,或者,可以考虑使用 对象池。

  1. 其他的一些造成 CPU 飙升的场景,比如  selector 空轮训导致 CPU 飙升 。

处理方式:参考 Netty 源码,无效的事件查询到了一定的次数,进行 selector 重建。

Java 的 CPU 飙升 700% 优化的真实案例

陈某提示:以下案例,来自互联网。大家参考一下,准备一个自己的案例。

最近负责的一个项目上线,运行一段时间后发现对应的进程竟然占用了 700% 的 CPU,导致公司的物理服务器都不堪重负,频繁宕机。

那么, 针对这类 java 进程 CPU 飙升的问题,我们一般要怎么去定位解决呢?

采用 top 命令定位进程

登录服务器,执行 top 命令,查看 CPU 占用情况,找到进程的 pid

top

很容易发现,PID 为 29706 的 java 进程的 CPU 飙升到 700% 多,且一直降不下来,很显然出现了问题。

使用 top -Hp 命令定位线程

使用 top -Hp 命令(为 Java 进程的 id 号)查看该 Java 进程内所有线程的资源占用情况(按 shft+p 按照 cpu 占用进行排序,按 shift+m 按照内存占用进行排序)

此处按照 cpu 排序:

top -Hp 23602

很容易发现,多个线程的 CPU 占用达到了 90% 多。我们挑选线程号为 30309 的线程继续分析。

使用 jstack 命令定位代码

  1. 线程号转换 5 为 16 进制

printf “%x\n” 命令(tid 指线程的 id 号)将以上 10 进制的线程号转换为 16 进制:

printf "%x\n"  30309

转换后的结果分别为 7665,由于导出的线程快照中线程的 nid 是 16 进制的,而 16 进制以 0x 开头,所以对应的 16 进制的线程号 nid 为 0x7665

  1. 采用 jstack 命令导出线程快照

通过使用 dk 自带命令 jstack 获取该 java 进程的线程快照并输入到文件中:

 jstack -l 进程ID > ./jstack_result.txt

命令(为 Java 进程的 id 号)来获取线程快照结果并输入到指定文件。

jstack -l 29706 > ./jstack_result.txt

  1. 根据线程号定位具体代码

在 jstack_result.txt 文件中根据线程好 nid 搜索对应的线程描述

cat jstack_result.txt |grep -A 100  7665

根据搜索结果,判断应该是 ImageConverter.run() 方法中的代码出现问题

当然,这里也可以直接采用

jstack <pid> |grep -A 200 <nid>

来定位具体代码

$jstack 44529 |grep -A 200 ae24
"System Clock" #28 daemon prio=5 os_prio=0 tid=0x00007efc19e8e800 nid=0xae24 waiting on condition [0x00007efbe0d91000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrentC.TimeUnit.sleep(TimeUnit.java:386)
    at com.*.order.Controller.OrderController.detail(OrderController.java:37)  //业务代码阻塞点

分析代码解决问题

下面是 ImageConverter.run() 方法中的部分核心代码。

逻辑说明:

/存储minicap的socket连接返回的数据   (改用消息队列存储读到的流数据) ,设置阻塞队列长度,防止出现内存溢出
//全局变量
private BlockingQueue<byte[]> dataQueue = new LinkedBlockingQueue<byte[]>(100000);
//消费线程
@Override
public void run() {
    //long start = System.currentTimeMillis();
    while (isRunning) {
        //分析这里从LinkedBlockingQueue
        if (dataQueue.isEmpty()) {
            continue;
        }
        byte[] buffer = device.getMinicap().dataQueue.poll();
       int len = buffer.length;
}

在 while 循环中,不断读取堵塞队列 dataQueue 中的数据,如果数据为空,则执行 continue 进行下一次循环。

如果不为空,则通过 poll() 方法读取数据,做相关逻辑处理。

初看这段代码好像每什么问题,但是如果 dataQueue 对象长期为空的话,这里就会一直空循环,导致 CPU 飙升。

那么如果解决呢?

分析 LinkedBlockingQueue 阻塞队列的 API 发现:

//取出队列中的头部元素,如果队列为空则调用此方法的线程被阻塞等待,直到有元素能被取出,如果等待过程被中断则抛出InterruptedException
E take() throws InterruptedException;
//取出队列中的头部元素,如果队列为空返回null
E poll();

这两种取值的 API,显然 take 方法更时候这里的场景。

代码修改为:

while (isRunning) {
   /* if (device.getMinicap().dataQueue.isEmpty()) {
        continue;
    }*/
    byte[] buffer = new byte[0];
    try {
        buffer = device.getMinicap().dataQueue.take();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
……
}

重启项目后,测试发现项目运行稳定,对应项目进程的 CPU 消耗占比不到 10%。

8种专坑同事的 SQL 写法,性能降低100倍,不来看看?
GPTs商店应用测评 - Sous Chef:根据您的口味和食材定制食谱
为什么没看到嘲笑外包的帖子了?网友:叫包哥~




点个在看你最好看

  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值