CPU使用率高一直是工程师头疼的问题,也是面试题中的常客,几乎所有的大厂在面试的时候都问过此类问题。
可能每个同学都看到过,甚至有过开发同学写到过的更加详细的分析过程。但是今天,小编会从一个运维的视角来告诉你该如何回答。
插播一条我自己的广告:如果您即将毕业,如果 您最近正面临跳槽找工作,那不妨关注我,这里整理了近几百道运维相关面试题,还在持续更新,面试前刷刷八股,提高面试通过率
面试提示
- 面试过程应该中,对于此类开放性问题,应优先给出解决和排查思路
- 至于具体的原因,需要根据自身掌握的知识体系选择性作答,不推荐回复自己完全陌生领域的内容,比如java gc,如果你不是java栈,或者没有接触过,那就灵活替换成自己熟悉的技术栈
- 不推荐回答自己陌生领域,也就意味着我推荐大家在回答问题的时候,有意识将问题引导向自己熟悉的领域
1 CPU占用高的分析脑图
2 面对CPU 100%后的通用法则
-
先处理问题,再排查原因
对于线上业务,作为运维,永远需要以保障线上业务稳定为第一要务。绝不可以无限期的以追查根因为目标来处理线上问题。当然非在线业务,则需要竭尽全力追查元凶
-
最可能保留现场
在响应和处理CPU高的问题时,要尽可能采集和保留当前系统环境,如系统资源状态,连接信息,进程内存dump信息等
-
迅速隔离
对于线上问题,单点cpu异常,则可以断定出现了偶发性问题,问题节点应该在保留现场后迅速隔离(如从线上离线、重启等),多节点通用问题,优先立即扩容
隔离方法:
- 节点从线上离线
- kill 进程ID
- 服务器重启
-
离线还原及复现
线上节点在隔离之后,就已经失去了故障发生时的状态,所以对于一些复杂问题,就需要在线上根据故障现场信息,进行环境重建和场景复现,分析和测试问题可能的原因
以上仅代表我个人过去多年运维经验的总结,如有更好或不同意见,请留言讨论。
3 状态采集及分析
3.1 了解我们的top命令
标记1 的行,为CPU的使用率详情,其中每个不同的指标一般代表着不同的原因
user
(us),代表用户态 CPU 时间。注意,它不包括下面的 nice 时间,但包括了 guest 时间。- us高则代表为某个用户进程占用的cpu过多,比如上图的标记 5 行,top会按照实际的CPU使用率进行排序,可以很方便的看到具体是哪一个进程占用的CPU过多
system
(sy),代表内核态 CPU 时间- 内核空间的cpu时间占用过多,包括系统调用、中断、驱动、内核级服务或者守护进程占用等
nice
(ni),代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU时间。这里注意,nice 可取值范围是 -20 到 19,数值越大,优先级反而越低。idle
(id),代表空闲时间。注意,它不包括等待 I/O 的时间(iowait),这个占用高代表的是空闲,而不是使用率,一般我们在配置告警的时候,也常会使用(1-id%)来计算CPU的使用率iowait
(wa),代表等待 I/O 的 CPU 时间- wa占用过多,这个大部分都是在等待磁盘I/O,如磁盘带宽、IOPS不足,或者I/O调度不合理或者文件系统问题等等
irq
(hi),代表处理硬中断的 CPU 时间- 硬中断数,一般硬中断引起的cpu使用率高的概率不多,因为一般设备硬中断的时间很少
softirq
(si),代表处理软中断的 CPU 时间。- 软中断高,一般都会是网卡出现了数据包突发,如网络pps突增,网卡中断绑定不均等
steal
(st),代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU 时间。- 这个一般在虚拟机化,云计算IaaS层会发生,当物理机出现cpu超售的时候,cpu被其他虚拟机争用的时候会出现。如果出现了这个高,请选择非突发性实例
标记2/3代表的是当前系统内存及swap交换分区使用情况,从左向右依次为,总内存,剩余内存,使用内存,用于buffer或cache的内存。
标记4 为每个进程占用CPU的比率,top会进行倒叙排列
标记5为top1的进程名称。
以上是我们在发现CPU异常时,可以通过top命令快速采集当前CPU占用最多的进程,以及具体是CPU的哪一部分占用高,根据以上的信息,我们需要能够快速的判断方向。
4 CPU 100%的原因分析
4.1 代码问题
4.1.1 业务死循环
代码死循环是出现频率很高的问题,对于一些循环的判断条件判断有误,循环无法跳出。在我们当前的生产环境下,之前就发生了在一个多层循环的最内层查询大量数据,而引发CPU耗尽
比如下边这个例子就会迅速让我们的CPU耗尽
import multiprocessing
import os
def process_task():
while True:
pass
if __name__ == "__main__":
num_processes = multiprocessing.cpu_count()
processes = []
for _ in range(num_processes):
p = multiprocessing.Process(target=process_task)
p.start()
processes.append(p)
for p in processes:
p.join()
4.1.2 死锁和等待
在生产环境下,当工作线程需要访问外部资源的时候,如果外部资源响应非常慢,就会让我们的工作线程一直处于等待状态,只要这样的等待任务够多,cpu就会被耗尽。
程序多个线程如果一直等待一个不被释放的锁,就会出现死锁,死锁实际上是一个无线等待。在之前做LVS负载军时,CPU访问共享连接表(竞争资源操作需要加锁)就会使用自旋锁,发生流量突发时,就会出现大量CPU消耗在自旋锁等待上。
4.1.3 冗余的不必要的业务逻辑
在我们当前的业务代码中,因为历史原因就存在大量的冗余逻辑,比如同步调用外部接口,同步执行大量的文件读写,无效数据库查询。
4.2 性能及容量问题
4.2.1 大量的并发任务
一般业务代码常会采用多进程或者多线程模型,系统中如果并发创建了非常多的线程,那线程间切换,线程对竞争资源的锁争用就会愈发明显。因为CPU是有限的,则cpu就不得不在众多的进程/线程间来回切换,最后cpu都消耗在了上下文切换上
解决思路:进程池或者线程池限制并发量
4.2.2 业务密集的CPU计算任务
这是一种常规性的cpu消耗,比如搜索推荐场景的模型训练、和推理,视频、图片编解码,大数据计算如hadoop的MapReduce任务,spark任务等,这些场景都是CPU密集型的业务。
碰到这种CPU密集型的业务,CPU 100%是很正常的,不必担忧,任务跑完之后,CPU自会下来。作为运维我们需要做的是将CPU密集型的业务尽可能独立于其他在线业务部署,或者通过cgroup对这些应用所使用的CPU进行限制。保证业务间不要相互影响
4.2.3 大量的网络请求
在我们前边讲到的Top中有一项是si
,软中断,多数情况都是服务器当前存在有网络请求突发。对于一些网络转发行的应用,比如典型的TCP层的负载均衡设备(LVS/Nginx/Haproxy)等,其核心能力就是数据包转发。
如果出现瞬间的网络流量突发,就会触发大量网卡中断,那SI的数量就可能会非常多,将CPU资源耗尽。在多核CPU下,网卡中断需要均匀的绑定到每一个CPU核心上,如果绑定不均,CPU的中断消耗就会不均,则经常会出现CPU核心一个接一个被依次打死【如下右图】。
cat /proc/interrupts |grep -i eth 可以查看当前各个CPU核心的网卡中断消耗
4.2.4 大量的TCP连接占用
如下图是在互联网中广泛存在的代理架构,比如我们haproxy,nginx等,正常情况下客户端连接proxy,proxy负载与后端的service节点建立连接,所以一个请求proxy需要建立两个连接。
正常情况下后端服务正常快速返回没有问题,但是如果后端服务出现慢请求一直等待,那proxy向后的连接就会一直被占用;还有客户端可能会因为访问超时而出现大量的重试请求。一般现在的proxy层都会分别与client和service维护长连接,那这个时候服务器上的连接数会被迅速拉升。
维护TCP连接数本身就会消耗一定的内存和CPU资源,再加上服务器的端口是有限的,在新建连接的时候tcp会从整个端口中查询可用端口(受local_port_range限制),这个过程在端口占用量很高的情况下,是非常消耗CPU的。
解决办法:
- 配置合理的连接超时时间,让连接可以快速回收,并配合使用tcp timewait reuse
- 合理配置与客户端以及与后端服务的长连接配置,合理控制长连接idle的超时时间
- 增加后端service的数量,IP或者端口,让五元组组合数更多
- 在service服务固定的情况下,可以增加proxy的source ip来临时性的解决连接数量的问题
4.3 资源问题
4.3.1 内存不足
在内存不足时,如果程序需要申请内存,系统就会进行内存swap,将内存的一部分数据交换出去,存储到磁盘的swap分区中,回收内存以满足程序的内存升级。
此时会有诸多原因造成CPU使用突发:
- 瞬间大量的内存数据交换到磁盘,I/O读写会很高,这个时候大量的CPU可能会消耗在wa上
- 现在大部分服务器是没有swap分区的,当没有足够内存的时候,会调用kswapd0内核线程执行内存交换,但是又没有swap分区可用,可能就会陷入一个死循环,如下图
4.3.2 Java进程的频繁GC
实际上很多语言都有GC(垃圾回收)的过程,其中java最具代表性,java的gc过程是非常消耗CPU资源的。
4.3.3 硬盘异常或I/O性能不足
如果硬盘故障,硬盘写入带宽不足,或者IOPS不足的情况下,如果碰上大量的随机读写,或者IOPS不足的情况下,CPU会出现大量的io wait,最终整个CPU会消耗殆尽。
如下图是我们在AWS上使用的RDS mysql实例,我们在创建之初使用的磁盘类型是gp2
,使用过aws的同学可能知道,这个类型的云盘iops是磁盘容量乘以3,也就是我100GB的磁盘空间,最高只有300 iops。
在iops不足的情况下,mysql的cpu就直接到了100%,后边的磁盘空间是aws rds为了满足需要的iops,进行了磁盘的自动扩容。
当然除了磁盘本身的性能限制之外,如果碰上磁盘故障,SSD写寿命到期,SSD写入不均衡,Raid卡设置或硬件异常(如raid缓存失效,或者配置了Write Through)都会造成磁盘读写性能下降,最终cpu耗尽。
怎么查询IO消耗:
- top中的cpu wa数量高,即可以判断是磁盘IO问题
- iotop工具分析io使用量最高的进程
解决方案:
- 检查硬盘硬件是否异常,读写性能(如通过smart,raid卡提供的工具等查看磁盘性能及状态信息)
- 选择性能更好的磁盘,如nvme ssd。对于高IO的业务,推荐使用本地盘服务器,而非云盘
- 对于云盘IO与磁盘空间相关的时候,为了获取更高的IOPS,可能就需要扩展更多的云盘空间了。
5 Java程序CPU问题该如何分析
这也会经常单独出现在各大厂的面试题中,因为java在大厂应用比较多
1、通过top找到CPU占用率高的进程
2、通过top -Hp pid命令查看CPU占比靠前的线程ID
3、再把线程ID转化为16进制
printf "0x%x" <thread_id>
如:printf "0x%x" 32 ==> 0x20
4、通过jstack生成线程dump文件
jstack -l <PID> > dump.log
5、分析线程dump文件
grep "0x<十六进制线程ID>" dump.log
如:grep "0x20" dump.log
这将显示包含该线程ID的线程栈信息。分析这些线程栈,找到可能导致CPU高占用的代码
6 总结
生产环境下下cpu 100%是一个常见问题,原因有非常多,作为运维当秉承着保障在线业务稳定性第一的原则,优先解决问题,而后协同研发排查。
至于cpu 100%的原因,常见的业务代码问题,网络突发,服务器容量不足,连接数超限,内存不足等等,生产环境下导致最终cpu 100%的原因还有很多,还需要各位同学在生产环境中多积累和总结。
7 写在最后
运维相关岗位面试时涉及到知识面非常广,所以复习起来会有无从下手的感觉。为了让同学们面试不迷茫,都能找到一份好工作,我们整理了运维面试的高频面试题,关注我们回复“面试题”免费领取。