线上 CPU 飙高如何排查?

693 阅读6分钟

CPU 使用率飙升通常是服务不稳定或系统负载过高的表现,可能会导致应用响应迟缓、请求超时等问题。对 CPU 飙高的排查需要迅速定位问题的根源,避免影响业务的正常运行。以下是对线上 CPU 飙高问题的排查步骤和常见原因。

1️⃣ 监控指标和日志分析

✅ 1.1 监控 CPU 使用情况

首先,使用监控系统(如 PrometheusGrafanaZabbix 等)查看服务器的 CPU 使用情况。

方案:

CPU 使用率:查看 CPU 的总体使用率,如果在短时间内异常高,首先要找出是哪个进程或服务导致。

CPU 核心使用情况:检查不同 CPU 核心的负载分布,确认是否有某些核心异常占用。

进程级别的 CPU 使用:查看占用 CPU 的进程或线程,定位到具体的服务或请求。

示例:使用 top 或 htop 查看实时 CPU 使用

top -i # 显示占用 CPU 的进程
✅ 1.2 查看应用日志

查看应用的日志文件,排查是否有异常或错误信息(如死循环、异常抛出等)。

方案:

应用日志分析:查看应用日志是否有异常堆栈、错误信息或警告,尤其是那些可能导致 CPU 使用异常的部分。

慢查询日志:对于数据库或缓存操作,查看是否有慢查询或性能瓶颈,导致过多 CPU 资源消耗。

✅ 1.3 查看系统负载与进程

查看系统级别的负载,使用系统工具监控实时资源消耗。

方案:

topps:查看进程的 CPU 使用情况,找出消耗 CPU 的进程。

iotop:监控磁盘 IO 使用,磁盘瓶颈可能间接导致 CPU 使用飙升。

vmstat:查看内存、交换空间(swap)等资源的使用情况,内存不足时可能会导致 CPU 负载飙升。

2️⃣ 定位导致 CPU 高负载的原因

✅ 2.1 死循环或过度计算

如果应用中有死循环或执行了大量计算任务,会导致 CPU 资源被耗尽。

方案:

排查死循环:查看是否有异常的代码逻辑,如死循环、递归调用等,尤其是如果出现单个请求导致的 CPU 飙升。

检查算法效率:检查是否有复杂度较高的算法,导致单个请求需要较长时间计算。优化算法或拆分任务。

示例:

// 避免死循环
while (true) {
    // 不断循环执行
}
✅ 2.2 线程或进程竞争

多线程应用中,线程竞争和上下文切换过多会导致 CPU 占用过高。

方案:

分析线程:检查是否存在线程竞争、死锁等问题。查看线程池配置是否合理,是否有线程阻塞导致 CPU 使用高。

线程分析工具:使用线程分析工具(如 jstackVisualVM)查看线程堆栈,分析线程的阻塞、等待等问题。

示例:

# 使用 jstack 获取堆栈信息
jstack <PID> > thread_dump.txt
✅ 2.3 高频繁的 I/O 操作

大量的磁盘或网络 I/O 操作会导致 CPU 资源被过度占用。

方案:

I/O 操作检查:查看是否有高频繁的磁盘读取、写入或网络通信,导致 CPU 使用过高。

优化 I/O 操作:减少不必要的磁盘操作、避免重复的网络请求,可以将缓存引入,以减少磁盘或网络访问。

示例:

# 使用 iotop 查看磁盘 I/O 情况
iotop -o
✅ 2.4 错误的负载均衡

如果服务的负载均衡配置不当,可能会导致某些实例 CPU 使用过高。

方案:

检查负载均衡配置:检查负载均衡的算法和策略,确保请求能均匀分配到各个实例,避免某些节点的 CPU 超载。

扩展应用实例:如果是流量激增,可以考虑通过扩容应用实例来分担负载。

✅ 2.5 外部系统依赖问题

如果依赖外部系统(如数据库、消息队列等),由于其性能瓶颈或不稳定,可能导致 CPU 资源消耗过高。

方案:

依赖监控:检查数据库、缓存、外部服务的性能,确保它们不会引起应用线程的阻塞,进而影响 CPU 使用。

优化调用逻辑:减少对外部系统的同步调用,采用异步、批量处理等方式。

3️⃣ 性能优化

✅ 3.1 优化数据库查询

数据库查询性能不佳,可能导致应用线程阻塞,CPU 占用过高。

方案:

索引优化:确保数据库查询使用了合适的索引,避免全表扫描。

查询优化:使用分页查询,避免一次性查询大量数据。

缓存优化:使用缓存(如 Redis)来减少对数据库的频繁访问,降低数据库压力。

✅ 3.2 应用性能调优

对于高 CPU 消耗的应用,可以进行性能调优,优化代码和业务逻辑。

方案:

代码优化:审查代码中的性能瓶颈,使用更高效的算法和数据结构。

线程池配置:合理配置线程池的大小,避免过多的线程导致上下文切换和资源竞争。

示例:

# 调整线程池大小
spring:
  task:
    executor:
      pool:
        core-size: 10  # 核心线程数
        max-size: 50  # 最大线程数
✅ 3.3 使用异步编程

将计算密集型或 I/O 密集型的任务改为异步执行,避免阻塞主线程,提高系统吞吐量。

方案:

异步处理:对于一些耗时的任务,如邮件发送、图片处理等,可以采用异步处理机制,避免影响主线程。

示例:

@Async
public void sendEmail(String email) {
    // 执行异步任务,如发送邮件
}
✅ 3.4 垃圾回收与内存管理

频繁的垃圾回收(GC)可能导致 CPU 使用率上升。检查是否存在大量对象创建和内存泄漏问题。

方案:

GC 优化:检查垃圾回收的日志,是否存在频繁的 GC,优化内存管理,减少对象创建和销毁。

内存泄漏:通过 VisualVMJProfiler 等工具分析应用的内存使用情况,确保没有内存泄漏。

4️⃣ 异常情况下的临时措施

✅ 4.1 降级与限流

当 CPU 使用率飙升时,可以采取降级和限流策略,防止系统进一步崩溃。

方案:

降级服务:暂时关闭某些非核心的功能,减轻服务器负载。

限流策略:使用限流器(如 令牌桶漏桶算法)对请求进行限流,避免超负荷请求导致 CPU 占用过高。

✅ 4.2 异常进程重启

如果某些进程或服务无法恢复,导致 CPU 高占用,考虑重新启动进程。

方案:

自动重启:设置进程监控机制,当 CPU 使用过高时,自动重启服务。

5️⃣ 总结:CPU 飙升问题排查的关键点

  1. 监控与日志分析:通过监控系统和日志文件快速定位 CPU 占用异常的进程或代码。

  2. 代码和逻辑分析:检查是否存在死循环、线程竞争、频繁的 I/O 操作等问题。

  3. 优化数据库与外部依赖:检查数据库查询、外部系统依赖,减少阻塞和性能瓶颈。

  4. 性能优化:优化代码、数据库、缓存,使用异步编程和合理的线程池配置。

  5. 临时措施:当问题无法立即解决时,采取限流、降级、重启等措施,避免系统崩溃。