文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

【压测】压测出现的问题剖析

核心思路:将系统视为一个整体

压测的本质是给系统一个持续的、高强度的负载,观察其反应。基础指标是系统的“生命体征”,它们的变化揭示了系统内部的处理能力和资源瓶颈。我们的目标是找到那个最先达到极限的“最短木板”。问题现象 -> 核心指标分析 -> 根因定位 -> 排查命令/思路


一、CPU 相关:计算与调度瓶颈

1. 现象:CPU 使用率持续高位(>80%)

  • 核心指标

    • %us(用户态CPU使用率):高
    • %sy(系统态CPU使用率):可能也高
    • Load Average(系统负载):通常也很高
    • Context Switch/s(上下文切换次数):可能很高
  • 详细问题分析

    • 问题A:应用代码计算瓶颈

      • 描述:你的业务逻辑本身非常复杂,例如大量的数学计算、复杂的序列化/反序列化(如JSON/Protobuf)、正则表达式匹配、加密解密等。
      • 根因:代码中存在性能热点,单个请求就需要消耗大量CPU时间。
      • 排查方向
        1. 使用性能剖析工具:这是最直接的方法。
          • Java:使用 arthasprofiler 命令,或 async-profiler 生成火焰图,直接看到代码热点。
          • Go:使用 pprof 进行CPU剖析。
          • 通用:perf(Linux)。
        2. 检查算法复杂度:是否存在O(n²)或更糟的算法?数据量增大时,CPU消耗是否呈指数级增长?
    • 问题B:垃圾回收(GC)压力

      • 描述:主要发生在JVM(Java)、Go(虽然Go的GC高效,但压力大时也会出现)、.NET等有自动内存管理的语言上。
      • 根因:短时间内产生大量短生命周期对象,导致GC频繁启动,而GC本身是CPU密集型的操作。
      • 指标特征:CPU使用率曲线呈“锯齿状”,内存使用曲线也可能呈锯齿状。JVM的GC日志会显示频繁的Young GC或Full GC。
      • 排查方向
        1. 查看GC日志:开启JVM的GC日志,分析GC频率、暂停时间。
        2. 监控堆内存:观察Eden区、Survivor区、老年代的内存变化。
        3. 优化代码:减少不必要的对象创建,使用对象池。
    • 问题C:锁竞争激烈

      • 描述:多线程环境下,大量线程在竞争同一把锁,导致大多数线程处于等待状态,而不是执行任务。
      • 根因:锁的粒度太粗,或者存在热点资源(如一个全局计数器)。
      • 指标特征%sys可能会比较高,因为锁的维护需要内核介入。Context Switch/sInvoluntary Context Switches/s(非自愿上下文切换)会非常高。但QPS可能很低,因为CPU时间都花在线程调度和等待上了。
      • 排查方向
        1. 使用线程分析工具jstack(Java)查看线程栈,会发现大量线程阻塞在同一个锁上。
        2. 优化锁策略:使用细粒度锁、无锁数据结构(如CAS)、或并发容器。

2. 现象:CPU 使用率低,但系统负载(Load Average)极高

  • 核心指标

    • %us%sy
    • Load Average 远高于CPU核心数(例如4核机器,负载达到20+)
    • %wa(I/O等待CPU时间百分比):高
  • 详细问题分析

    • 问题:I/O 瓶颈(最常见)
      • 描述:进程不占用CPU是因为它们大部分时间都在等待慢速的I/O操作(磁盘、网络)完成。这些等待的进程/线程处于可运行队列中,推高了负载。
      • 根因:见下文【磁盘I/O】和【网络I/O】章节。CPU空闲是因为“工人们”都在排队等“原材料”(数据)到来。
    • 排查方向:立即检查磁盘和网络指标。

二、内存相关:内存与交换分区瓶颈

1. 现象:内存使用率线性增长直至耗尽(OOM)

  • 核心指标

    • Used Memory 持续增长,Available Memory 趋近于0。
    • 【容器环境】容器内存使用量接近其 limit
  • 详细问题分析

    • 问题A:内存泄漏

      • 描述:应用代码中,被分配的内存由于编程错误(如全局Map未清理、监听器未注销等)而无法被垃圾回收器回收。随着时间推移,内存被逐步啃食殆尽。
      • 根因:代码Bug。
      • 排查方向
        1. 生成堆转储:在内存快满时,使用 jmap(Java)或类似工具生成堆转储文件。
        2. 使用内存分析工具:用MAT、JProfiler等工具分析堆转储,找出占用内存最大的对象和其引用链,定位泄漏点。
    • 问题B:缓存失控

      • 描述:本地缓存(如Guava Cache、Caffeine)没有设置合理的容量上限或过期时间,导致缓存数据无限增长。
      • 根因:配置不当或缓存策略有误。
      • 排查方向:检查缓存配置,设置 maximumSizeexpireAfterWrite/Access

2. 现象:Swap 使用量显著增加

  • 核心指标

    • Swap Used 增加
    • 磁盘I/O写入量 增加(因为Swap在磁盘上)
    • 系统响应时间急剧上升且不稳定
  • 详细问题分析

    • 问题:物理内存不足
      • 描述:当物理内存不足时,操作系统会将物理内存中不活跃的页面换出到磁盘的Swap区域,以腾出空间。
      • 影响:一旦某个被换出的内存页需要被访问,系统会产生一个“缺页中断”,并从磁盘Swap区将其换入。这个磁盘I/O操作比内存访问慢几个数量级,导致性能雪崩式下降。
    • 排查方向:同【现象1】,解决内存泄漏或优化内存使用。在生产环境中,通常建议禁用Swap,因为其带来的性能抖动是不可接受的。

三、磁盘 I/O 相关:持久化瓶颈

1. 现象:磁盘利用率 100%

  • 核心指标

    • %util:100%
    • await(平均I/O等待时间):非常高
    • svctm(平均I/O服务时间):可能很高
    • r/sw/s(IOPS) 或 rMB/s, wMB/s(吞吐量) 达到磁盘上限
  • 详细问题分析

    • 问题A:日志输出过于频繁
      • 描述:应用在DEBUG或INFO级别下打印了过多日志,且日志文件没有配置合理的滚动策略(Rolling Policy)。
      • 根因:日志配置不当,或代码中存在循环内打日志等不良实践。
    • 问题B:数据库压力大
      • 描述:数据库正在进行大量写操作(如批量插入、更新)或读操作(全表扫描),且磁盘性能(特别是IOPS)是瓶颈。
      • 根因:SQL查询未优化、索引缺失、缓冲区配置不合理。
    • 问题C:应用本身频繁写文件
      • 描述:如图片/文件上传服务、数据导出服务等。
  • 排查命令iostat -x 1iotop


四、网络 I/O 相关:通信瓶颈

1. 现象:网络带宽达到网卡上限

  • 核心指标

    • rxkB/stxkB/s 接近网卡理论速度(如千兆网卡约115MB/s)。
    • drop(丢包数)可能增加。
  • 详细问题分析

    • 问题:应用本身就是高带宽类型的,如视频流、大文件传输。
    • 排查方向:优化数据传输(如压缩)、使用更高带宽的网络。

2. 现象:TCP 连接数异常

  • 核心指标

    • TCP 连接数,特别是 TIME_WAIT 状态连接非常多。
    • 可能出现 Cannot assign requested address 错误。
  • 详细问题分析

    • 问题A:TCP短连接
      • 描述:HTTP客户端没有使用连接池,每个请求都创建新的TCP连接,用完即关。关闭的连接会进入 TIME_WAIT 状态(持续2MSL,通常为1-4分钟),大量此类连接会耗尽客户端端口。
      • 根因:编程模型或配置错误。
    • 问题B:连接池配置不当
      • 描述:连接池的最大连接数设置过小,导致请求在池外排队,可能引发超时,并可能诱发客户端创建更多短连接。
  • 排查命令ss -s(查看总结), netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'(查看各状态连接数)

3. 现象:TCP 重传率高 / 丢包严重

  • 核心指标

    • retrans(重传报文数)高。
    • retrans/s(每秒重传数)高。
  • 详细问题分析

    • 问题:网络不稳定、拥塞。
    • 影响:重传会导致请求延迟(RTT)大幅增加。
    • 排查命令netstat -s(查看TCP统计信息)

五、最隐蔽的情况:所有资源都“空闲”,但性能不达标

  • 现象

    • CPU、内存、磁盘I/O、网络带宽使用率均不高。
    • 系统负载(Load Average)也正常。
    • 但压测QPS就是上不去,响应时间(RT)却很高。
  • 详细问题分析

    • 问题A:外部依赖瓶颈

      • 描述:你的应用线程在等待下游服务(如数据库、Redis、第三方API)的响应。此时你的应用线程处于阻塞等待状态,不消耗CPU,看起来“很闲”。
      • 根因:下游服务处理能力不足、慢查询、或网络延迟高。
      • 排查方向
        1. 链路追踪:使用SkyWalking、Zipkin等工具,分析请求时间到底消耗在哪个下游服务上。
        2. 监控下游:直接监控数据库的CPU、慢查询日志;监控Redis的响应时间。
    • 问题B:应用内部资源池瓶颈

      • 描述:应用配置的数据库连接池、HTTP客户端连接池的线程数/连接数已满。新的请求需要排队等待池中有资源释放。
      • 根因:资源池大小配置过小,或池中连接被长时间占用(这又可能指向下游服务慢)。
      • 排查方向:查看应用资源池的监控指标(如HikariCP、Druid的监控界面),看活跃连接数、等待线程数。
    • 问题C:压测客户端成为瓶颈

      • 描述:你用来发压的机器(压测机)本身性能不足(CPU、网络),无法产生足够的压力。
      • 排查方向:监控压测机本身的资源使用情况。解决方案是使用分布式压测,或者用更高配置的压测机。

总结与实战排查流程

  1. 全局观览:使用 tophtopvmstat 1 快速查看四大核心资源(CPU, Memory, IO, System)的总体情况,找到最明显的瓶颈点。
  2. 深入剖析
    • CPU高 -> 使用 profiler 工具分析代码热点。
    • 负载高但CPU低 -> 使用 iostatsar -n DEV 1 检查I/O。
    • 内存增长 -> 使用内存分析工具找泄漏点。
    • 所有资源都低 -> 检查下游依赖和内部资源池。
  3. 关联分析:结合应用日志(特别是错误日志、超时日志)和链路追踪数据,将系统指标与业务逻辑错误联系起来,形成完整的证据链。

通过这种由浅入深、由表及里的分析,你可以精准地定位压测中暴露出的性能瓶颈,并进行有效的优化。

posted @ 2025-09-29 15:55  NeoLshu  阅读(16)  评论(0)    收藏  举报  来源