返回文章列表

文章

分析服务器CPU占用过高

按“业务层面”、“系统层面”和“外部/恶意因素”三个大类来详细说明常见原因,并给出相应的诊断思路与优化建议

目录
  1. 以下情况都可能导致服务器上的 CPU 利用率飙升甚至长期保持高位。下面按“业务层面”、“系统层面”和“外部/恶意因素”三个大类来详细说明常见原因,并给出相应的诊断思路与优化建议。
  2. 一、业务层面
  3. 1. 高并发请求导致瞬时/持续计算压力
  4. 二、系统层面
  5. 1. 系统级调度与内核消耗
  6. 三、外部/恶意因素
  7. 1. 短期爆发性流量(爬虫、刷票)
  8. 四、诊断思路
  9. 五、常见优化思路
  10. 1. 优化业务代码
  11. 2. 调优 JVM 参数
  12. 3. 合理配置线程池
  13. 4. 系统层面调优
  14. 5. 应对外部恶意流量
  15. 六、示例:在分片上传业务中 CPU 高占用如何排查与优化
  16. 七、总结
  17. 📎 参考文章

以下情况都可能导致服务器上的 CPU 利用率飙升甚至长期保持高位。下面按“业务层面”、“系统层面”和“外部/恶意因素”三个大类来详细说明常见原因,并给出相应的诊断思路与优化建议。#

一、业务层面#

1. 高并发请求导致瞬时/持续计算压力#

  • 大量并发 HTTP 请求
    • 当用户量激增(比如电商秒杀、热点新闻刷屏、大流量文件上传/下载等),瞬时并发请求数远超服务器处理能力时,线程/进程会不断创建、切换,造成 CPU 上下文切换增多,进而使 CPU 利用率飙升。
    • 举例:在分片上传业务中,如果数千个客户端同时开始上传分片,Spring Boot 线程池可能被迅速耗尽,触发大量新线程创建,导致系统切换消耗增加。
  • 复杂业务逻辑、密集型计算
    • 耗时加密/解密:如对文件进行分块加密、签名或使用 SSL/TLS 协议时,每个请求需要多次加解密运算,会占用显著 CPU。
    • 大规模数据/文件处理:例如在内存中对大文件做哈希计算(MD5/SHA-1)、图片/视频转码、压缩、实时预览或格式转换,都会耗费大量 CPU 资源。
    • 矩阵运算、图像处理、机器学习模型推理:如果服务端一并承担数据分析、图片识别等任务,也会迅速推高 CPU 使用率。
  • 死循环或递归调用过深
    • 代码逻辑写得不够严谨,出错时陷入无限循环(如 while(true) 未及时 break),会导致线程一直消耗 CPU。
    • 递归层级太深或终止条件写错,也会让 CPU 一直执行函数调用而不上下文切换。
  • 频繁的垃圾回收(GC)
    • 大对象/频繁分配:Java 程序如果频繁创建短生命周期对象(如大量 String 拼接、临时 POJO 等),会促使 JVM 不断触发 Minor GC,甚至 Full GC。Full GC 会暂停业务线程,在 GC 期间 CPU 会主要用于 GC 算法本身,导致整体 CPU 利用率高而且吞吐下降。
    • 堆内存配置不合理:堆过小让 GC 过于频繁;堆过大则单次 GC 时间过长,也会拉高 CPU 使用。
  • 线程池配置不当
    • 线程过多:将线程池设置得太大,超过 CPU 核数×2~3 的范围时,会造成大量上下文切换(context switch),附加开销带来更高的 CPU 占用。
    • 线程饥饿或阻塞:线程池中大量线程被 IO/锁/网络请求阻塞,造成核心业务线程一直处理排队任务,从而导致 CPU 只能在少数活跃线程间切换,但整体吞吐受限,反而也会出现高峰时的 CPU 利用率。

二、系统层面#

1. 系统级调度与内核消耗#

  • 高 I/O 请求转化为 CPU 消耗
    • 例如大文件读写,尽管主要是磁盘 I/O,但内核需要处理文件系统缓存、拷贝数据到用户空间、更新元数据,也会占用 CPU。
    • 当存储介质(SSD/HDD)性能不足时,会让用户态线程频繁进入内核态等待,内核态下的处理也会推高 CPU 占用。
  • 内核/驱动层 bug 或异常
    • 某些场景下,如果网卡驱动、磁盘驱动有缺陷或网络抖动严重,内核可能频繁重试中断(Interrupt),造成中断风暴(Interrupt Storm),CPU 负载骤增。
    • 虚拟化环境(如 Docker、KVM、VMware)也可能因虚拟化层配置不当,导致宿主机和虚拟机之间内核调度开销变高。
  • Docker/虚拟化容器资源争抢
    • 当多容器共用同一台物理机,且没有对 CPU quota、cgroup 限制,容器间的 CPU 竞争会加剧,某个容器如果跑出“CPU 飙升”,会影响整台宿主机上的 CPU 利用率。
  • 操作系统后台任务
    • 日志轮转/备份:crontab 定时执行大规模备份、日志切割、压缩归档任务时,短时间内大量文件操作会拉高 CPU。
    • 系统监控/安全软件扫描:例如 auditdclamav 等安全审计、杀毒程序,进行实时扫描时也会占用一定 CPU。
    • 定期更新/补丁:如 yum updateapt-get upgrade 等在后台执行时,伴随解压、编译等操作,也会短暂拉升 CPU。

三、外部/恶意因素#

1. 短期爆发性流量(爬虫、刷票)#

  • 网络爬虫/爬虫集群
    • 大量爬虫对接口进行疯狂抓取,会触发后端大量业务逻辑计算和数据库查询,进而增大 CPU 压力。
    • 特别是爬虫在短时间内不断发并发请求,还可能对缓存击穿,导致业务层直接落库、落库查询,进一步拉高 CPU。
  • DDoS 攻击、恶意请求
    • 放大攻击(UDP 洪泛、SYN flood、HTTP Flood 等)会令服务器不断处理半开连接、丢弃请求或进行加密握手,消耗 CPU。
    • HTTP 层面的恶意请求(如不断访问同一接口并携带复杂参数,触发后端复杂计算),也会让 CPU 达到 100%。
  • Botnet 或刷量工具
    • 一些刷单、刷票等脚本会大量请求下单、登录、评论等接口,触发后端处理,导致正常业务线程吃不消,CPU 长时间高负载。

四、诊断思路#

当发现服务器 CPU 长期占用在 80%~100% 不下,需要尽快定位原因。下面提供一个相对通用的排查流程:

  1. 初步监控
    • 使用 tophtop 检查整体 CPU 使用率、各进程占比。
      • top 里查看 %Cpu(s)、load average;按 P 排序可看到哪个进程占比最高。
      • htop 可以更加直观地查看每个线程的 CPU 使用情况(F2→Display options 打开线程视图)。
    • 如果发现 Java 进程(通常显示为 java 或你的应用名)占用异常高,说明大概率是应用层面问题;如果多个系统进程或内核线程占用,则可能是系统/内核层面。
  2. 细化到线程级别(针对 Java 服务)
    • 使用 jstack <pid> > threaddump.txt 导出线程堆栈,看哪些线程长期处于 RUNNABLE 状态且在执行热点代码(如自循环、IO、加密算法等)。
    • 还可以结合 Linux 下的 top -H -p <pid>,查看不同线程(线程号以十进制显示)的 CPU 占用,将它们与 jstack 结果里每个线程的 nid(native thread id,十六进制)对应,就能定位到底是哪段代码运行最耗时。
  3. 分析 GC 情况
    • 如果 CPU 高占用与 GC 日志(开启 Xlog:gc* / Xloggc:...)重叠,可能是频繁 GC 导致。
    • 通过 jstat -gc <pid> <interval> 可以实时查看堆空间、GC 次数和停顿时间,若 Full GC 频繁且一次停顿较长(例如每秒都 Full GC),也会占用大量 CPU。
  4. 抓包/流量分析
    • 使用 tcpdumpiftopnload 等工具查看网络流量是否瞬时飙升,判断是否有爬虫/攻击流量拉升 CPU。
    • 如果确实有大量来自同一 IP 或某些区域的异常请求,需要在 Nginx/负载均衡层面做限流或黑名单。
  5. 系统日志与监控
    • 查看 /var/log/messages/var/log/syslog/var/log/kern.log 中是否有异常内核告警,比如 I/O 错误、网络错误、驱动重载等。
    • 检查是否有 oom-killer 报错(Out-Of-Memory),虽然这是内存不足,但有时系统疯狂 swap 也会间接拉高 CPU。
    • 如果是容器化部署,可看 Docker 日志,或用 docker stats 观察各容器的 CPU 使用情况。
  6. 数据库/外部依赖分析
    • 虽然“慢 SQL”一般体现在磁盘 I/O 或数据库 CPU,但在应用层如果写了大量同步查询并在代码里循环发起,会让业务线程阻塞,然后在回复后集中触发大量后续计算,造成 CPU 瞬时峰值。
    • 检查是否有“死锁”或“长时间等待锁”的情况,导致大量线程都在等待,然后一旦锁释放,所有线程一起竞争 CPU。

五、常见优化思路#

1. 优化业务代码#

  • 避免不必要的循环与深度递归:审查是否有 while(true)for(;;) 没有及时退出的风险,改成安全的、有限次迭代机制。
  • 减小对象创建频率:尽量复用对象(如使用对象池、字节缓冲池、StringBuilder 等),减少短生命周期对象,降低 GC 频率。
  • 批量处理、延迟计算:如果某些操作可以批量一次性完成(如批量写数据库/文件),就不要每次请求单独触发计算;或对于可延迟的耗时计算,使用异步线程/消息队列,从主线程剥离计算压力。
  • 限流与降级:对高并发场景(如大文件分片上传),可以在接口层做限流(令牌桶、漏桶算法),防止瞬间大流量直达后端。若检测到系统负载过高,可快速返回限流或“稍后再试”的提示,避免用户重试产生更大压力。

2. 调优 JVM 参数#

  • 合理设置堆大小:通过压测观察 GC 日志,调整 XmsXmxXX:NewRatioXX:MetaspaceSize 等,使 Young/Old 区和元空间均衡,减少 Full GC。
  • 选择合适的垃圾收集器:对于在线业务,一般推荐使用 G1/GraalVM ZGC/Epsilon(视 JDK 版本而定),它们对暂停时间更友好;如果是需要最大吞吐,可以考虑 Parallel GC。
  • 监控 GC 日志:持续关注 GC 次数、停顿时间,必要时加上可视化监控(如 GCViewer、VisualVM、jstat 等工具)。

3. 合理配置线程池#

  • 线程池大小
    • CPU 密集型任务,线程数≈CPU 核数 + 1;
    • IO 密集型任务,可以适当设置为 CPU 核数×(1 + 阻塞系数),如阻塞系数 0.5~1。
  • 使用专门的异步任务队列
    • 对于分片上传业务,可把“保存分片”“计算哈希”“写数据库”等异步出来,交给单独的线程池处理,避免主线程阻塞造成 CPU 调度混乱。

4. 系统层面调优#

  • 升级内核与驱动:保持 Linux 内核、网卡驱动、存储驱动更新,以避免已知的 interrupt storm、网络抖动等问题。
  • 限制容器/进程的 CPU 使用:通过 cgroup(或 Docker 的 -cpus)限制单个服务最多使用的 CPU 核心数,避免某个进程“吃满”整台物理机。
  • 开启 NUMA/CPU 亲和性:对于多核服务器,可以通过 taskset 或在 JVM 启动参数里设置 XX:ActiveProcessorCount,指定程序使用指定核,减少跨 NUMA 域内存访问带来的性能开销。
  • 监控与告警:集成 Prometheus + Grafana、ELK 等监控系统,设置 CPU 利用率阈值告警。在告警触发后及时分析并扩容或限流。

5. 应对外部恶意流量#

  • 前端限流/验证码:在 Nginx 层面配置 limit_req_zonelimit_conn,防止同一 IP 的请求过于频繁。同时可以对敏感接口加验证码、OAuth 验证等。
  • WAF/防火墙:启用 Web 应用防火墙(如 ModSecurity、云厂商自带 WAF),拦截常见的 DDoS、爬虫、SQL 注入等恶意访问。
  • 黑/白名单策略:对已知的爬虫 IP、扫描 IP 进行黑名单封禁,将正常业务方 IP 拉进白名单,优先保障合法流量。

六、示例:在分片上传业务中 CPU 高占用如何排查与优化#

假设你的 Spring Boot 服务在分片上传高峰期,CPU 一直在 90% 以上。可参考以下步骤逐步定位与优化:

  1. 使用 ****top -H -p <pid>, 找到占用最高的线程 ID(十进制)。
  2. 执行 ****jstack <pid> > dump.txt,在 dump.txt 里找到对应线程的 hex nid,然后转换成十六进制(例如 nid=0x3f2b),查看它正在执行哪段代码。
  3. 如果发现大量线程都在同一方法(如文件写入、哈希计算、数据库保存),说明这些操作是 CPU 瓶颈。
  4. 优化思路举例
    • 将文件哈希计算改为异步线程完成,只在最后合并完成时再校验。
    • 对上传分片进行“先异步落盘+异步写元数据”再返回前端,减少同步等待。
    • 对数据库写操作加批量处理:不每次分片都发送一次插入请求,而是按每 50 个分片一次性写入。
    • 在 Nginx 层做基本速率限制,防止短时间内大量重试请求压垮后端。
    • 检查 JVM GC 日志,适当增大堆内存或更换垃圾收集器,避免 GC 过程占用过多 CPU。

七、总结#

  1. 什么时候会 CPU 占用过高?
    • 业务层面:高并发请求、密集计算逻辑、死循环/递归、垃圾回收压力过大、线程池配置不当。
    • 系统层面:内核中断风暴、驱动 bug、容器化资源争抢、系统后台任务(备份、日志轮转、安全扫描)。
    • 外部/恶意:爬虫/刷量工具、DDoS 攻击、暴力请求、SQL 注入扫描等。
  2. 如何诊断?
    • 先用 top/htop 定位是哪个进程/线程;再用 jstack(Java 进程)结合线程 ID 分析热点代码;观察 GC 日志;抓包看流量情况;检查系统日志。
  3. 怎么优化?
    • 优化热点业务逻辑、减少不必要的循环和对象创建、使用异步/批量处理;
    • 调整 JVM 参数、使用合适的 GC 策略;
    • 合理配置线程池、限制容器 CPU 配额、开启 CPU 亲和;
    • 在 Nginx/防火墙层做限流、WAF、黑白名单,过滤恶意流量;
    • 持续监控、告警,并根据真实业务负载做水平/垂直扩容。 只要系统出现 CPU 长时间、持续、接近或达到饱和(80%~100%)的情况,就应参照以上思路快速排查、定位,并从代码、配置、系统、网络等各个层面进行综合优化。这样既能降低单机的 CPU 压力,也能保证整体服务的稳定性与可扩展性。

📎 参考文章#