当前位置: 首页 > 后端技术 > Java

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

时间:2023-04-01 20:16:59 Java

大家好,我是陈~[星球]()小伙伴面试网易,遇到一个性能相关的面试题:CPU飙升900%,怎么办?很遗憾,这家伙并没有理想地回答上述问题。最终,他的网易之旅在第二面结束。可惜关注公众号:码猿技术专栏,回复关键词:1111获取阿里内部Java性能调优手册!首先说明一下问题:CPU飙升200%以上是生产中很容易出现的场景:1:MySQL进程飙升900%大家都在用MySQL进程,想必大家都遇到过CPU突然变得过高,或者达到200%以上的情况。当数据库进行查询或数据修改操作时,系统需要消耗大量的CPU资源来维护存储系统和内存中数据的一致性。当并发量大,大量SQL执行性能低下时,比如字段没有索引,快的CPU会飙升。如果还启用了慢速日志记录,性能将进一步恶化。有一个MYSQL在生产中飙升900%的糟糕情况。场景二:Java进程飙升900%。一般来说,Java进程不会做大量的CPU操作。正常情况下,CPU占用率应该在100%到200%之间。大量的GC容易出现这种CPU飙升,CPU飙升900%是完全有可能的。其他场景:其他类似Redis、Nginx等进程暴涨900%的场景陈提醒:你介绍场景的时候说主要涉及两个场景,Java进程暴涨900%,MySQL进程飙升了900%。其实这两个场景就足够聊很久了。其他人,只是使用规避技术来避免它。场景一:MySQL进程CPU飙升到900%,怎么办?定位进程:使用top命令观察,判断是mysqld还是其他原因造成的。如果是mysqld引起的,showprocesslist,查看session状态,判断是否有耗费资源的SQL在运行。找到高消耗的sql,查看执行计划是否准确,是否缺少索引,或者是数据过多导致的。处理过程:杀掉这些线程(同时观察cpu使用率是否下降),一般来说,必须杀掉这些线程(同时观察cpu使用率是否下降),并做相应的调整(比如加索引,改sql,改内存参数),然后重新运行这些SQL。是否缺少索引,做相应的调整(比如加索引,改sql,改内存参数),如果是,则建立索引。也有可能每个sql并没有消耗多少资源,但是突然间,连接了大量的session,cpu飙升。这种情况下,就需要分析为什么连接数会随着应用激增,然后做出相应的调整。比如限制连接数这样的优化过程,往往不是一步完成的,而是一步一步的,实现一个优化的写法,然后观察,再优化。场景一真实案例:MySQL数据库优化真实案例陈提醒:以下案例均来自网络。作为参考,准备一个你自己的案例。我自己也遇到过这个问题。之前开发同事写的SQL语句导致线上CPU过高,MySQL的CPU占用率达到了900%+,最后通过优化降低到70%~80%。说说我个人在这个过程中的考察思路。首先,我们需要定位问题,而不是盲目启用慢日志。在大量SQL并发量大、性能低下的情况下,启用慢日志无意将MySQL推向崩溃的边缘。遇到这种情况,我分析了当前的数据量、索引情况、缓存使用情况。目测数据量并不大,只有几百万。接下来就是定位索引和缓存的问题了。查询后发现,很多查询都是通过MySQL进行的,并没有使用缓存。由于没有使用缓存,是大量请求全部查询MySQL造成的。通过以下命令查看:showprocesslist;发现有很多类似的SQL语句,一直处于查询状态。选择idformuserwhereuser_code='xxxxx';初步分析可能是user_code字段没有索引造成的。然后查询user表的索引:showindexformuser;发现这个字段没有被索引。添加索引后,SQL查询就可以正常执行了。3、过了一段时间,又出现大量请求超时的问题。然后经过分析,发现是开启了慢日志查询。大量的SQL查询语句超过慢日志设置的阈值,所以关闭慢日志后,速度瞬间提升。CPU使用率基本维持在300%左右。但这还不理想。4、紧接着,部分实时查询数据的SQL语句是通过缓存(redis)读写实现的。观察一段时间后,基本维持在70%~80%。总结:其实这次事故的解决办法很简单,就是加个索引,结合缓存使用。CPU占用率过高时不建议开启slowlog。因为大量的请求,如果真的是日志慢的问题,就会写日志磁盘,性能会极低。直接通过MySQL的showprocesslist命令查看,基本上可以很清楚的定位到一些查询问题比较严重的SQL语句,然后对SQL语句进行分析。一般可能是索引、锁、查询大量字段、大表等问题造成的。此外,必须使用缓存系统来减少对MySQL的查询频率。对于内存调优,也是一种解决方案。场景二展开:Java进程CPU飙升到900%,怎么办?定位进程:定位CPU飙升问题的一般步骤是:首先,使用top命令查看当前占用高CPU的进程的PID;查看当前消耗资源进程的线程PID:top-HpPID使用print命令将线程PID转为16进制,根据16进制值在打印的栈日志中查询,查看线程所在方法位置居住。使用jstack命令查看堆栈信息,定位到线程对应的具体代码。分析代码解决问题。处理过程:如果是空循环,或者空自旋。处理方法:可以使用Thread.sleep或者lock让线程正常阻塞。在循环的代码逻辑中,创建大量新对象会导致频繁的GC。比如从mysql中查到大量数据,比如100多W等等。处理方法:可以减少创建对象的数量,也可以考虑使用对象池。其他一些导致CPU飙升的场景,比如selector空转训练导致CPU飙升。处理方法:参考Netty源码,如果查询到一定次数的无效事件,就会重新构建选择器。JavaCPU优化700%飙升真实案例陈提醒:以下案例来自网络。作为参考,准备一个你自己的案例。最近,我负责的一个项目上线了。运行一段时间后,发现对应进程占用了700%的CPU,导致公司物理服务器不堪重负,频繁关机。那么,对于这种java进程CPU飙升的问题,我们一般是怎么定位和解决的呢?,使用top命令定位进程并登录服务器,执行top命令,查看CPU使用情况,找到进程的pidtop,很容易查到,PID为的java进程的CPU29706已经飙升到700%多了,一直没能降下来,明显有问题question。使用top-Hp命令定位线程使用top-Hp命令(Java进程的id号)查看Java进程中所有线程的资源使用情况(按shft+p按cpu使用率排序,按shift+m按内存使用率排序Sorting)这里按cpu排序:top-Hp23602很容易发现多线程的CPU使用率达到了90%以上。我们选择线程号为30309的线程继续分析。使用jstack命令定位代码1.将线程号5转为十六进制printf"%x\n"命令(tid指的是线程的id号)将上述十进制线程号转为十六进制:printf"%x\n"30309转换后的结果分别为7665。由于导出的线程快照中线程的nid是16进制的,而16进制是以0x开头的,对应的16进制的线程nid是0x76652。使用jstack命令导出线程快照使用dk自带的命令jstack获取java进程的线程快照并输入到文件中:jstack-lprocessID>./jstack_result.txt命令(进程的id号Java进程)获取线程快照结果并输入到指定文件。jstack-l29706>./jstack_result.txt3.根据线程号定位具体代码根据线程nid在j??stack_result.txt文件中查找对应的线程描述catjstack_result.txt|grep-A1007665根据搜索结果,应该是ImageConverter.run()方法中的代码有问题当然也可以直接使用jstack|grep-A200定位具体代码$jstack44529|grep-A200ae24"SystemClock"#28daemonprio=5os_prio=0tid=0x00007efc19e8e800nid=0xae24等待条件[0x00007efbe0d91000]java.lang.Thread.State:TIMED_WAITING(sleeping)atjava.lang.Thread.sleep(本机方法)在java.lang.Thread.sleep(Thread.java:340)在java.util.concurrentC.TimeUnit.sleep(TimeUnit.java:386)在com.*.order.Controller.OrderController.detail(OrderController.java:37)//业务代码阻塞点分析代码问题解决下面是ImageConverter.run()方法中的部分核心代码。逻辑说明:/存放minicap的socket连接返回的数据(使用消息队列存放读取的流数据),设置阻塞队列长度防止内存溢出//全局变量privateBlockingQueuedataQueue=newLinkedBlockingQueue(100000);//消费者线程@Overridepublicvoidrun(){//longstart=System.currentTimeMillis();while(isRunning){//这里从LinkedBlockingQueue分析if(dataQueue.isEmpty()){continue;}byte[]buffer=device.getMinicap().dataQueue.poll();intlen=buffer.length;}在while循环中,不断读取阻塞队列dataQueue中的数据,如果数据为空,执行continue进行下一个循环。如果不为空,则通过poll()方法读取数据,并做相关逻辑处理。乍一看,这段代码似乎有些问题,但是如果dataQueue对象长期为空,这里就会出现空循环,导致CPU飙升。那如果解决了呢?分析LinkedBlockingQueue阻塞队列的API发现://取出队列中的头部元素。如果队列为空,调用该方法的线程将被阻塞等待,直到可以取出一个元素。如果等待进程被中断,一个InterruptedExceptionEtake()throwsInterruptedException;//取出队列中的头部元素,如果队列为空,则返回nullEpoll();这两个值的API,显然take方式更适合这里的场景。代码修改为:while(isRunning){/*if(device.getMinicap().dataQueue.isEmpty()){continue;}*/byte[]buffer=newbyte[0];尝试{buffer=device.getMinicap().dataQueue.take();}catch(InterruptedExceptione){e.printStackTrace();}...}重启项目后,测试发现项目运行稳定,对应项目进程的CPU消耗占比不到10%。来源:技术自由圈硬道理(勿嫖,求关注)小陈的每篇文章都是用心输出,如果这篇文章对你有帮助或者启发,请点赞,关注,转发,收藏,你的支持是我坚持下去的最大动力!关注公众号:【码猿技术专栏】,公众号有给力的粉丝福利,回复:进群,可以加入技术讨论群,和大家一起讨论技术,吹牛!