1. CPU 占用率高

当系统响应变慢,监控显示 CPU 持续飙升到 90%+ 时,通常遵循以下排查思路:

排查步骤

  1. 定位进程:使用 top 命令查看哪个进程占用 CPU 最高。

  2. 定位线程:使用 top -Hp <pid> 查看该进程下哪个线程最耗资源。

  3. 获取线程 ID:将线程 ID 转换为 16 进制(例如 printf "%x\n" <tid>)。

  4. 查看堆栈:使用 jstack <pid> | grep -A 20 <16进制线程ID> 打印堆栈信息,直接定位到出问题的代码行。

常见原因与对策

  • 死循环或逻辑漏洞:代码中出现了没有出口的 while 循环。

    • 策略:通过 jstack 定位代码行,修复循环退出条件。如果是由于多线程并发修改非线程安全集合(如 HashMap)导致的死循环,改用 ConcurrentHashMap

  • 频繁 GC(尤其是 Full GC):JVM 频繁回收垃圾会导致 CPU 飙升。此时需要用 jstat -gcutil <pid> 查看 GC 情况。

    • 策略

      1. 代码层:减少短时间内产生大量临时对象(如在循环内 new 对象)。

      2. 调优层:增大堆内存 -Xmx,或更换更低延迟的垃圾回收器(如从 CMS 换成 G1ZGC)。

  • 计算密集型任务:如大规模加解密、序列化、复杂的正则匹配。

    • 优化:改异步处理、引入缓存或使用更高效的库。

    • 策略:使用更高效的序列化协议(如 Protobuf 代替 JSON/XML);对正则表达式进行预编译(Pattern.compile)。

  • 锁竞争激烈:大量线程在竞争同一个锁,导致上下文切换频繁。

    • 策略

      1. 线程池调优:不要创建过多线程。

      2. 改用无锁编程:使用 CAS (Atomic 类) 代替 synchronizedReentrantLock

2. 数据库 SQL 查询时间长

分析工具

  • 慢查询日志 (Slow Query Log):首先通过日志找到执行时间超过阈值(如 500ms)的 SQL。

  • EXPLAIN 执行计划:这是核心工具,重点看 type(访问类型)、key(命中索引)和 rows(扫描行数)。

EXPLAIN详细介绍

当拿到一个慢 SQL,第一步永远是查看执行计划。需要重点关注以下几个字段:

  • type (连接类型):代表查询效率。从好到坏依次为:

    • system > const (主键/唯一索引等值查询) > eq_ref > ref (非唯一索引扫描) > range (范围扫描) > index (全索引扫描) > ALL (全表扫描)。

  • key:实际使用的索引。如果是 NULL,说明没用到索引。

  • rows:预计要扫描的行数。数值越小越好。

  • Extra (额外信息)

    • Using index覆盖索引(好,不需要回表)。

    • Using filesort文件排序(坏,需要在内存或磁盘排序,应通过索引优化)。

    • Using temporary临时表(坏,常见于 GROUP BYDISTINCT)。

常见优化策略

  • 索引优化

    • 检查是否命中索引:避免全表扫描(type=ALL)。

    • 最左前缀法则:联合索引要遵循字段顺序。

    • 避免索引失效:不要在索引列上做运算、函数操作或使用 !=LIKE '%xxx'

  • 查询重写

    • 减少返回字段:禁止 SELECT *,只取需要的列(覆盖索引,避免了“回表”操作)。

    • 深分页优化LIMIT 100000, 10 会扫描前 10 万行。建议记录上次最大 ID,走 WHERE id > last_id LIMIT 10

    • 大表 JOIN 优化:小表驱动大表(Straight Join),确保被驱动表的关联字段上有索引(触发 Index Nested-Loop Join

  • 架构层面

    • 读写分离:主库写,从库读。

    • 分库分表:数据量达到千万/亿级时考虑水平拆分。

    • 引入缓存:Redis 挡在数据库前面。

典型问题

  • 全表扫描(ALL)

    • 策略:针对 WHEREORDER BY 涉及的字段建立索引

  • 回表(Lookup)频繁

    • 策略:建立覆盖索引(Covering Index)。比如查询 SELECT a, b FROM table WHERE a = 1,则建立联合索引 (a, b),这样数据直接从索引树获取,无需回表查整行

  • 深分页(Deep Pagination)

    • 策略:使用“标签查询”:SELECT * FROM t WHERE id > 100000 LIMIT 10。或者先查出 ID 再 JOIN

  • 临时表与排序

    • 策略

      1. GROUP BY 的字段确保有索引。

      2. 适当增大 sort_buffer_size 内存缓冲区。

  • 锁等待(Lock Wait)

    • 策略

      1. 尽量缩短事务长度。

      2. 在高并发更新下,将一行数据拆分成多行(如分段汇总)减少行锁竞争。

3. 内存问题

主要表现为 OutOfMemoryError (OOM) 或频繁的内存抖动。

排查工具

  • jmapjmap -dump:format=b,file=heap.hprof <pid> 导出堆快照。

  • MAT (Memory Analyzer Tool):分析快照,查看哪个对象占用了绝大部分内存。

典型场景

  • 内存泄漏 (Memory Leak):长生命周期的对象持有短生命周期对象的引用(如静态集合类不断添加元素)。

    • 策略

      1. 静态集合:及时清理不再使用的 static List/Map。

      2. 资源关闭:使用 try-with-resources 确保数据库连接、I/O 流被关闭。

      3. ThreadLocal:手动调用 remove() 防止线程复用导致的内存泄漏。

  • 大对象申请:一次性从数据库查出几十万条数据到内存。

    • 策略

      1. 分页获取:严禁 SELECT * 且不加限制。

      2. 流式处理:处理大文件使用 InputStream 逐行读取,不要一次性加载到内存。

  • 元空间溢出:动态生成了太多的类(如反射、CGLIB 重复创建)。

    • 策略:减少动态代理类的生成;调大 -XX:MaxMetaspaceSize

4. 磁盘 I/O 与网络 I/O

如果 CPU 并不高,但系统响应极慢,通常卡在 I/O 上。

  • 磁盘 I/O 瓶颈:使用 iostat -x 1 查看 %util

    • 优化:减少同步写盘、批量写入、使用 SSD。

    • 策略

      1. 批量读写:使用 BufferedOutputStream,或在数据库中使用批量插入 batch insert

      2. 异步刷盘:将同步写操作改为消息队列(MQ)异步处理。

      3. 零拷贝(Zero Copy):在高性能传输场景下利用 FileChannel.transferTo()

  • 网络 I/O 瓶颈

    • 检查是否存在大量短连接(导致 TIME_WAIT 状态过多)。

    • 检查内网带宽是否打满。

    • 策略

      1. 连接池:使用数据库连接池(Druid/HikariCP)和 HTTP 长连接,减少 TCP 三次握手。

      2. 压缩传输:开启 Gzip 压缩,减少传输字节数。

      3. 合并请求:前端或服务间调用时,能批量获取不单点调用。

5. 系统性能

性能测试

性能测试不仅是看系统能扛多少量,更重要的是找到系统的拐点(Bottleneck)

核心方法

  • 基准测试(Baseline):在单用户或低负载下运行,确定系统运行的最佳状态。

  • 压力测试(Load Testing):逐步增加用户数,直到系统达到预定的性能指标(如 CPU 80% 或 响应时间 500ms)。

  • 极限压测(Stress Testing):继续加压,直到系统报错、崩溃或拒绝服务,观察系统的恢复能力

  • 稳定性测试(Soak Testing):在固定高负载下运行 24-72 小时,排查内存泄漏和资源溢出。

推荐工具

  • JMeter:功能最全,支持插件多,后端面试最常问到的工具。

  • Locust:基于 Python,支持协程,适合模拟超大规模(10万+)并发。

  • K6:现代化的性能测试工具,使用 JS 编写脚本,对开发者非常友好。

性能监控

监控就像是系统的“仪表盘”,主要分为四个层级:

A. 基础设施层 (Metrics)

  • 指标:CPU 利用率、内存使用、磁盘 I/O、网络流量。

  • 工具Prometheus + Grafana(黄金组合)。Prometheus 抓取数据,Grafana 负责精美的可视化大屏。

B. 应用性能监控 (APM)

  • 指标:方法调用耗时、JVM 状态、线程池负载、数据库连接池。

  • 工具

    • SkyWalking:国产之光,支持 Java 字节码增强,无侵入监控。

    • Arthas:阿里开源的诊断工具,支持线上直接监控方法耗时trace 命令)和查看热点代码。

C. 链路追踪 (Tracing)

在高并发微服务架构中,必须知道请求在哪个服务卡住了。

  • 工具JaegerZipkin。通过 TraceID 将整个调用链路串联起来。

D. 日志分析 (Logging)

  • 工具ELK Stack (Elasticsearch, Logstash, Kibana)。用于分析慢 SQL 日志或异常堆栈。

高并发情况下的关键指标

指标

含义

优秀标准(参考)

Throughput (吞吐量)

单位时间内处理的请求数 (QPS/TPS)。

越高越好,需达到业务预估的 2-3 倍。

Response Time (RT)

请求从发出到收到响应的时间。

互联网业务通常要求 P99 < 500ms。

Error Rate (错误率)

失败请求占总请求的比例。

正常状态应 < 0.1%。

P99/P999

99% 的请求都在此时间段内完成。

消除“长尾效应”,确保绝大多数用户体验良好。

如果压测时发现 TPS 吞吐量上不去,但 CPU 占用率也很低,从哪些方面排查?

  • 检查线程池/连接池:是否配置太小,导致请求在排队。

  • 检查锁竞争:是否存在严重的 synchronized 导致的线程阻塞。

  • 检查外部依赖:下游服务或数据库 I/O 是否达到了瓶颈。

  • 网络带宽:内网网卡带宽是否打满。

保护措施

当监控发现系统快撑不住时,需要采取保护措施:

  • 限流 (Rate Limiting):超过 QPS 阈值的请求直接返回失败,保护下游服务(常用 SentinelGuava RateLimiter)。

  • 熔断 (Circuit Breaker):当某个下游服务慢或报错多时,暂时断开调用,避免雪崩效应

  • 降级 (Fallback):核心业务保命,非核心业务关停(例如:双 11 为了保证支付,可以暂时关闭评论功能)。