尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从CPU利用率到性能瓶颈:深入理解Linux系统CPU时间统计与排查实践

从CPU利用率到性能瓶颈:深入理解Linux系统CPU时间统计与排查实践 1. 从一次“句柄数不足”的故障说起为什么需要理解CPU利用率最近在排查一个线上服务的问题日志里频繁出现“句柄数不足”的报错同时监控大盘显示CPU使用率在80%到90%之间高位徘徊。很多初级工程师的第一反应可能是“CPU快用满了服务快撑不住了赶紧扩容” 或者 “句柄数不够调大ulimit限制。” 但实际情况往往更复杂。经过深入分析我们发现根本原因并非CPU计算资源真的不足而是一个第三方库在频繁地进行非阻塞I/O操作由于连接池配置不当产生了大量处于CLOSE_WAIT状态的TCP连接导致文件描述符句柄被快速耗尽。系统为了维护这些“半死不活”的连接内核的软中断softirq处理进程ksoftirqd的CPU消耗异常增高从而拉高了整体的CPU使用率指标。这个案例清晰地揭示了一个核心问题我们每天在监控系统上看到的那个“CPU利用率”百分比它到底意味着什么是CPU真的在“辛苦计算”还是像上述案例一样在“空转”或处理一些低效的内务对于开发、运维乃至架构师而言如果不能穿透这个简单的百分比数字理解其背后的操作系统原理和计算逻辑就很容易被表象误导做出错误的容量评估、性能调优甚至故障决策。今天我们就抛开那些监控工具的黑盒深入到操作系统内部手把手拆解“带操作系统的CPU利用率计算”到底是怎么一回事。无论你是正在备考操作系统面试的校招生还是需要处理性能问题的资深工程师理解这套机制都将让你对系统的运行状态有更本质的把握。2. 核心概念厘清用户态、内核态与CPU时间片在讨论CPU利用率之前我们必须先建立几个基石性的概念。现代操作系统无论是Windows、Linux还是其他类Unix系统为了安全性和稳定性普遍采用了特权级保护模型通常将运行空间划分为用户态和内核态。用户态是我们编写的应用程序比如你的Java后端、Python脚本、Nginx进程直接运行的环境。在这个态下程序只能执行普通的指令访问受限的内存空间。如果你想做任何“特权”操作比如读写磁盘文件、申请更多内存、发送网络数据包对不起你没这个权限。这就像在一个公司里普通员工用户态进程不能直接调用公司的核心资源如财务系统、服务器机房必须通过提交申请系统调用给行政部门内核来办理。内核态则是操作系统的核心代码运行的特权模式。它掌管着所有的硬件资源CPU、内存、磁盘、网卡和核心数据结构进程表、文件系统、网络协议栈。只有在内核态代码才能执行所有CPU指令访问任何内存地址。那么一个用户程序如何完成一次文件读取呢这个过程必然涉及从用户态到内核态的切换你的程序调用read()函数这是一个封装好的系统调用接口。CPU执行一条特殊的指令如syscall或int 0x80这会触发一个软中断硬件会自动将CPU从用户态切换到内核态。内核接管控制权找到你进程的文件描述符通过磁盘驱动从硬盘读取数据到内核缓冲区。数据准备好后内核将数据从自己的缓冲区复制到你进程在用户空间指定的缓冲区。内核执行返回指令CPU从内核态切换回用户态你的read()调用返回程序继续执行。这个切换过程是有开销的包括保存和恢复寄存器、更新页表、刷新TLB等。因此过于频繁的系统调用例如逐字节读取文件会成为性能瓶颈。理解了态切换我们再来看CPU是如何“同时”运行成百上千个进程的。这依赖于分时复用技术。操作系统内核中有一个叫做调度器的组件它会将CPU的物理时间划分为极短的时间片段通常几毫到几十毫秒称为时间片。每个可运行的进程被分配一个时间片当时间片用完或者进程主动放弃CPU比如等待I/O调度器就会暂停当前进程保存其上下文寄存器值、程序计数器等然后选择另一个就绪进程恢复其上下文并让其运行。这个过程就是上下文切换。上下文切换同样需要进入内核态来操作也是有开销的。所以当我们说“CPU利用率”时本质上是在说在一个特定的统计周期内比如1秒、5秒CPU执行有效指令的时间占总时间的比例。这里的“有效指令”既包括执行你的业务逻辑代码用户态也包括执行操作系统内核代码内核态如处理系统调用、中断、调度。而上下文切换、等待I/O等CPU空闲或不能用于执行进程指令的时间则不被计入“利用”时间。3. 实操解析Linux下的CPU利用率计算与查看理论讲完了我们到实战中看看。Linux系统为我们提供了多种视角来观察CPU利用率每一种都对应着不同的计算原理和粒度。3.1/proc/stat最原始的数据源一切故事的起点都在/proc/stat这个伪文件里。它提供了自系统启动以来CPU时间的累积值。cat /proc/stat你会看到类似如下的输出cpu 17936218 12786 5846321 170077721 87642 0 234001 0 0 0 cpu0 4481234 3197 1461580 42519430 21910 0 58500 0 0 0 cpu1 4481501 3196 1461582 42519431 21911 0 58500 0 0 0 ...第一行“cpu”是所有逻辑核的汇总后面的cpu0、cpu1等是每个独立逻辑核的统计。每一行的数字单位是USER_HZ通常为1/100秒即10毫秒依次代表user (17936218): CPU在用户态执行非nice进程的时间。nice (12786): CPU在用户态执行nice进程低优先级的时间。system (5846321): CPU在内核态执行的时间。idle (170077721): CPU空闲时间。注意等待I/O磁盘、网络的时间也包含在这里这是很多人的误区。idle意味着CPU确实没事可做连中断处理都没有。iowait (87642): CPU空闲但同时有未完成的磁盘I/O请求的时间。这是一个子状态包含在idle中但被单独列出来警示I/O瓶颈。irq (0): 处理硬件中断的时间。softirq (234001): 处理软中断的时间。网络包处理、定时任务等常在这里体现。steal (0): 在虚拟化环境中被宿主机“偷走”的时间你的虚拟机无法运行因为宿主机在服务其他虚拟机。guest (0): 运行虚拟CPU的时间。guest_nice (0): 运行低优先级虚拟CPU的时间。如何计算过去一段时间的CPU利用率监控工具如top,sar正是通过周期性采样/proc/stat来计算的。假设我们间隔1秒采样两次T1时刻total1 user1 nice1 system1 idle1 iowait1 irq1 softirq1 steal1T2时刻total2 user2 nice2 system2 idle2 iowait2 irq2 softirq2 steal2那么在这1秒内CPU总时间增量total_delta total2 - total1CPU非空闲时间增量busy_delta total_delta - (idle2 - idle1) - (iowait2 - iowait1)? 等等这里要小心。更精确的计算是busy_delta (user2-user1)(nice2-nice1)(system2-system1)(irq2-irq1)(softirq2-softirq1)(steal2-steal1)。因为iowait是idle的子集计算非空闲时间时通常不包含它它代表的是“因等待I/O而空闲”的时间。过去1秒的CPU利用率utilization busy_delta / total_delta * 100%注意iowait高并不意味着CPU忙恰恰相反它意味着进程经常在等待慢速的磁盘I/O导致CPU无事可做。但高iowait通常是系统I/O瓶颈的标志。3.2top与htop动态实时视图top命令是最常用的实时性能监控工具。它展示的CPU行就是基于上述原理计算出的瞬时或短期平均利用率。top - 10:30:01 up 30 days, 2:15, 1 user, load average: 1.02, 0.85, 0.72 Tasks: 256 total, 1 running, 255 sleeping, 0 stopped, 0 zombie %Cpu(s): 12.5 us, 6.2 sy, 0.0 ni, 80.1 id, 1.2 wa, 0.0 hi, 0.0 si, 0.0 st ...us (user): 用户态CPU时间占比对应/proc/stat的user。sy (system): 内核态CPU时间占比对应system。ni (nice): 低优先级用户进程占比对应nice。id (idle): 空闲占比对应idle。wa (iowait): I/O等待占比对应iowait。hi (hardware IRQ)si (software IRQ): 硬/软中断占比对应irq和softirq。st (steal): 被偷走的时间占比对应steal。htop是top的增强版提供了更直观的彩色显示、横向柱状图以及更方便的进程操作。它能更清晰地展示每个CPU核心的独立利用率对于发现CPU负载不均的问题非常有帮助。3.3mpstat与pidstat专业级细分统计mpstatsysstat包的一部分是更专业的每CPU统计工具。mpstat -P ALL 1 5这个命令会每隔1秒采样一次共采样5次并显示每个CPU核心的详细统计。这对于诊断多核CPU中某个核心被“打满”而其他核心空闲的问题可能由进程CPU亲和性设置或某个单线程热点导致至关重要。pidstat则专注于进程级别的统计它能告诉你具体是哪个进程消耗了多少用户态和内核态CPU。pidstat -u 1 5结合-d参数还可以看进程的I/O结合-w看上下文切换次数是性能剖析的利器。3.4 一个关键误区CPU利用率高等于性能瓶颈吗不一定。这需要结合其他指标综合判断负载均衡Load Average这个1分钟、5分钟、15分钟的平均值表示的是处于可运行状态和不可中断睡眠状态的平均进程数。如果1分钟负载远高于CPU核心数且CPU利用率很高那很可能CPU是真正的瓶颈。如果负载高但CPU利用率低则瓶颈可能在I/O或锁竞争上。上下文切换率Context Switch Rate可以通过vmstat 1或pidstat -w查看。如果上下文切换频率极高例如每秒数十万次即使CPU利用率不高性能也会因为大量的切换开销而下降。这可能是由过多的活跃进程进程爆炸或不当的锁机制如大量线程争用自旋锁引起的。系统调用频率使用strace或perf可以跟踪进程的系统调用。如果某个操作如日志写入触发了巨量的write系统调用即使每次调用消耗的CPU时间很少累积起来也会导致可观的sy系统态时间并伴随大量的上下文切换。回到开头的案例我们就是通过pidstat发现某个进程的sy占比异常再结合netstat发现大量CLOSE_WAIT连接并最终用lsof和代码审查定位到连接池未正确释放的问题。修复后sy和wa指标双双下降整体CPU利用率回归正常句柄数泄露问题也得以解决。4. 深入内核机制调度器、中断与CPU统计要真正理解CPU利用率数字的来源我们需要再往下走一层看看Linux内核是如何“记账”的。4.1 调度器与时钟中断CPU本身不会自动报告“我刚刚执行了进程A的代码”。这个记账工作是由操作系统通过时钟中断来驱动的。每个CPU核心都有一个本地时钟中断源如APIC以固定频率例如CONFIG_HZ250即每秒250次触发中断。每次时钟中断发生时内核的中断处理程序就会执行其中一项重要工作就是更新当前进程的时间统计。如果当前CPU正运行在用户态执行进程代码它就给当前进程的utime用户态时间加1个“滴答”。如果运行在内核态正在处理系统调用或中断就给stime内核态时间加1。同时全局的/proc/stat中的对应计数器也会增加。4.2 中断处理硬中断与软中断中断是打破进程调度、让CPU立即去处理紧急事件如网卡收到数据包、磁盘IO完成的机制。它分为两部分上半部硬中断在中断禁止的上下文中快速执行只做最紧急的工作如将网卡数据拷贝到内核缓冲区并标记一个软中断耗时必须极短。它的时间会计入/proc/stat的irq。下半部/软中断为了不长时间屏蔽中断耗时的处理工作被推迟到软中断中执行。内核有多个软中断向量如NET_RX网络接收、NET_TX网络发送、TIMER定时器等。ksoftirqd内核线程就是专门处理软中断的。它的时间会计入softirq。一个网络高吞吐场景的典型表现si软中断占比会非常高可能伴随us用户态占比不高。因为数据包在内核协议栈的处理校验和、拆包、递送到socket缓冲区都是在软中断中完成的。此时虽然CPU利用率数字很高但瓶颈可能不在应用层计算而在网络栈或网卡性能本身。4.3 内核统计代码窥探我们可以通过Linux内核源码来验证这个记账过程以较新的内核版本为例。在kernel/sched/cputime.c文件中可以找到account_user_time()、account_system_time()等函数它们正是在更新进程和全局的CPU时间计数器。而kernel/softirq.c中的__do_softirq()函数则会更新软中断的时间统计。理解到这个层次当你在top里看到si异常时就能立刻联想到可能是网络或块设备驱动层的问题进而使用ethtool、sar -n DEV或iostat等工具进行下一步排查。5. 高级话题与监控实践5.1 容器化环境下的CPU利用率在Docker、Kubernetes等容器环境中CPU利用率计算变得更加复杂。容器本质上是共享宿主机内核的一组带有资源限制的进程。传统的top和/proc/stat看到的是宿主机的全局视图。对于容器内的监控你需要使用cgroup的统计信息CPU使用时间记录在容器的cgroup目录下如/sys/fs/cgroup/cpu,cpuacct/container_id/cpuacct.usage。这是该容器内所有进程使用的CPU时间纳秒数包括用户态和系统态。通过计算两个时间点的差值再除以时间间隔和CPU核心数可以得到相对准确的容器CPU利用率。利用容器运行时或编排器的工具docker stats或kubectl top pods命令提供的CPU百分比就是基于cgroup数据计算得出的。它们比直接看宿主机top更准确反映了容器的真实资源消耗。注意CPU配额Quota和周期Period通过cpu.cfs_quota_us和cpu.cfs_period_us可以限制容器在period时间内最多使用quota的CPU时间。此时即使宿主机有空闲CPU容器也无法使用超过其配额。监控时需要关注的是已使用配额占配额的百分比而非宿主机全局利用率。5.2 性能剖析Profiling与火焰图当CPU利用率高成为明确瓶颈后下一步就是找出热点代码。采样分析工具是终极武器。perfLinux内核自带的强大性能分析工具。perf top可以实时查看函数级别的CPU占用。perf record可以录制性能数据然后用perf report分析。火焰图Flame Graph由Brendan Gregg发明的可视化性能剖析方法。它可以将perf等工具采集的堆栈采样数据渲染成一张直观的“火焰”图。y轴表示调用栈深度x轴表示采样到的次数即耗时。最宽的“火苗”就是最耗CPU的函数。它能够快速定位是用户态函数还是内核函数、是哪个调用链导致了CPU消耗。使用perf生成CPU火焰图的简化命令流# 1. 对指定进程进行采样 perf record -F 99 -p PID -g -- sleep 30 # 2. 生成报告数据 perf script out.perf # 3. 使用FlameGraph工具包折叠堆栈并生成SVG ./stackcollapse-perf.pl out.perf out.folded ./flamegraph.pl out.folded cpu_flamegraph.svg打开SVG文件你就可以交互式地查看CPU时间的分布精准定位性能热点。5.3 监控系统集成与告警策略在生产环境中我们不会一直盯着top。通常会将CPU利用率指标采集到监控系统如Prometheus中并配置告警。关键的告警策略建议避免对“CPU利用率”单一指标设置静态阈值如80%。因为业务有波峰波谷白天80%可能正常凌晨80%可能就是异常。应该使用基于历史数据的动态基线告警当利用率显著偏离同时间历史规律时才触发。关联告警将CPU利用率与应用层指标如QPS、响应时间、错误率关联。如果CPU利用率升高但QPS和响应时间不变可能只是后台任务无需立即告警。如果CPU升高的同时响应时间飙升、错误率增加这才是需要紧急处理的事故。细分告警分别对us、sy、wa、si设置告警。例如sy持续过高可能预示着系统调用频繁或锁竞争wa过高预示磁盘瓶颈si过高可能预示网络攻击或配置问题。使用饱和度指标除了使用率运行队列长度vmstat中的r列或负载是更好的饱和度指标。它直接反映了等待CPU的进程数。即使CPU使用率不到100%如果运行队列持续超过CPU核心数的数倍也意味着服务正在排队用户体验会受损。6. 从理论到实践一个完整的CPU性能问题排查流程让我们模拟一个真实的线上问题串联运用本文的知识点。场景夜间收到告警某API服务的P99响应时间从50ms飙升到2sCPU监控显示整体利用率达到92%。第一步全局概览登录服务器快速执行top。观察%Cpu(s)行发现us占65%sy占25%id仅剩10%。用户态和内核态都很高。load average 3分钟负载为15而机器是8核。负载远高于核数。进程列表有一个Java进程的CPU占用持续在280%8核机器最高800%。初步判断有一个Java应用线程在疯狂消耗CPU且内核态开销也不小。第二步深入进程分析使用pidstat -u -p PID 1 5确认该进程的%usr和%system贡献。使用pidstat -w -p PID 1 5发现该进程的cswch/s自愿上下文切换和nvcswch/s非自愿上下文切换都非常高达到每秒数万次。高上下文切换是sy时间高的一个可能原因。第三步定位热点与调用链使用perf top -p PID实时观察。发现热点集中在java.util.concurrent.ConcurrentHashMap的某个方法和一些锁操作上。为了得到更清晰的视图使用perf record录制30秒数据并生成火焰图。火焰图清晰地显示大部分CPU时间花在了ConcurrentHashMap.computeIfAbsent方法以及与之相关的锁等待和内核的futex快速用户态互斥锁系统调用上。第四步结合日志与代码分析查看应用日志发现大量与缓存相关的WARN日志。审查代码发现一段逻辑在每个请求处理中都会调用cache.computeIfAbsent(key, k - createExpensiveValue(k))。而createExpensiveValue是一个耗时的数据库查询操作。在高并发下大量线程同时查询同一个不存在的key导致全部卡在computeIfAbsent的锁竞争上同时触发大量线程挂起和唤醒操作引发了海量的上下文切换和futex系统调用。根因与修复 问题的根本是缓存穿透。大量请求同时查询一个不存在的数据导致耗时操作被重复执行且引发锁竞争。 修复方案短期在缓存层为“空值”也设置一个短暂的过期时间如30秒避免大量请求穿透到数据库和计算逻辑。长期使用互斥锁如Redis的SETNX在应用层对“构建缓存”这个动作进行串行化或者引入布隆过滤器预先判断key是否存在。验证修复发布后再次观察监控。CPU利用率下降至35%us占30%sy占5%负载降至2左右P99响应时间恢复至60ms。pidstat显示上下文切换率下降了两个数量级。通过这个流程我们不仅解决了问题更验证了从全局指标CPU利用率、负载到细节指标进程状态、上下文切换再到代码热点火焰图的层层下钻的分析方法是行之有效的。对CPU利用率计算原理的深刻理解是构建这套分析方法的地基。它让你看到的不是一个孤立的、令人恐慌的92%数字而是一张清晰的系统运行地图指引你快速找到问题的真正源头。
返回列表