☰
Java性能压测实战:从压测类型到瓶颈定位与优化落地
2026/9/26 2:33:44 网站建设 项目流程

1. 压测到底解决什么问题:先分清四种压测类型

我做Java性能压测这些年,最深的一个体会是:压测不是拿工具把请求怼上去、看系统什么时候挂掉那么简单。它真正解决的是"线上流量还没到,我就已经知道系统会在哪里先倒下"的问题。你可以在凌晨三点从容地观察CPU飙到100%、Redis连接池被打穿、数据库慢SQL把整个链路拖垮,而不是在大促当天的流量高峰里手忙脚乱地翻日志。这种提前量,就是压测最大的价值。

1.1 为什么线上系统总在流量高峰"掉链子"

很多团队对性能的认识停留在"代码能跑就行",直到线上出事故才意识到问题的严重性。我见过一个典型的例子:某个订单系统的接口平时扛200 QPS没问题,结果活动预热阶段流量涨到800 QPS,系统直接雪崩。事后定位发现,问题根本不在接口代码,而在一个毫不起眼的定时任务——它每5分钟做一次全表扫描,平时没人在意,但流量一上来,数据库锁竞争加剧,定时任务和业务查询互相拖累,最后所有线程都堆积在数据库连接池等待上。

这就是压测存在的意义:它让你在系统还健康的时候,就从外部流量视角看到内部资源的真实承载边界。压测过程中发现的问题,往往不是"某个接口写崩了"这种显性Bug,而是延迟、排队、资源竞争、GC停顿等只在高并发下才暴露的隐性缺陷。这类问题最大的特点是:单测、功能测试、代码评审全都发现不了,只有把流量真实地打上去,才能让它们浮出水面。

1.2 压力测试、负载测试、容量测试、稳定性测试的选型差异

很多人把"压测"当成一个笼统的词,实际上不同的测试类型解决的是完全不同的问题。我平时在团队里会按这张表来选型:

测试类型核心问题典型做法关注指标
压力测试系统在多高流量下开始出错或崩溃持续加压,直到错误率上升错误率、TPS拐点、RT激增点
负载测试系统在预期流量下能否稳定运行按预估峰值流量并发运行P99 RT、TPS达标率、资源利用率
容量测试系统的最大承载能力是多少阶梯式加压,找到吞吐量上限最大TPS、最大并发数、资源瓶颈位置
稳定性测试系统在持续压力下是否会劣化用70%~80%的极限流量跑数小时内存趋势、GC频率、连接池回收情况

这里有一个经验之谈:线上事故中最常见的不是瞬间压垮,而是持续运行几小时后缓慢劣化。比如内存泄漏、连接池回收不及时、临时对象过多导致GC频率持续升高,这些都需要稳定性测试才能暴露。我见过一个团队只做压力测试,每小时只测10分钟,结果每次测试都达标,但上线跑了两天OOM了——就是因为长尾状态下的资源堆积根本没进过测试视野。

所以选型时别只盯着"压力测试"一项。如果目标是排查性能瓶颈,优先做负载测试;如果要评估系统能接住多大的流量,做容量测试;如果要验证几天内的稳定性,做稳定性测试。四类测试是互补关系,而不是替代关系。

2. 正式压测前必须做好的三件事:环境隔离、数据造备、监控铺底

压测结果要让人信服,前置准备比压测本身更关键。很多压测报告被质疑"数据不可信",原因往往不在工具,而在三类准备没做到位。

2.1 压测环境和生产环境的差异,是最容易踩的坑

理想状态下压测环境要和生产环境在硬件配置、网络拓扑、依赖组件版本上完全对等,但实际情况很难做到。比较实际的策略是:明确记录差异,并在结论中补偿。

我在一家中型公司做压测时,测试环境的应用服务器是4核8G,生产是16核32G,数据库配置也不一样。这种情况下如果直接拿测试环境的TPS当生产容量,误差会非常大。我的做法是:先在同一套压测方案里跑两个环境的基准结果,算出一个经验换算系数——比如测试环境单机500 QPS,生产单机2000 QPS,系数就是4。之后每次优化验证都先看测试环境相对提升比例,再换算到生产预估。虽然这个系数不够精确,但比"拿测试环境结果直接上线决策"靠谱得多。

还有一个容易忽略的坑是容器环境下的资源隔离问题。如果应用跑在Docker或Kubernetes里,压测时要确认CPU限额、内存限额是否和生产一致,有没有被其他容器的资源争抢干扰。我看过一个案例,压测时应用的QPS忽高忽低,排查半天发现是同宿主机上有另一个容器在做大量IO操作,产生了资源竞争。所以压测前最好在高负载时段先做一次资源基线检测,确认环境没有"邻居噪声"。

2.2 压测数据怎么造才不失真

压测数据失真对结果的误导,比我见过的任何环境问题都更隐蔽。最典型的两种失真场景:

  • 数据量太小导致索引失效问题没暴露。生产环境某张表可能有上千万行,压测环境只有几万行。这时候SQL走全表扫描也很快,索引问题完全看不出来。但同样的SQL上线后,面对千万级数据量立刻变成慢SQL。
  • 数据分布不均匀导致缓存命中率失真。压测工具如果用随机参数打接口,而生产环境的请求参数往往符合特定分布(例如热点数据集中在少数几个ID上),那测试出来的缓存命中率会偏离真实的缓存效果,Redis单点压力也测不准。

我的造数策略有两步:第一步,从生产库做脱敏抽样导入,保证数据量和数据分布都接近真实;第二步,针对热点场景单独造一批"集中分布"的数据,模拟真实请求倾斜。压测脚本里的请求参数不能全部随机,而是按一定比例走热点参数,这样才能还原真实的缓存命中和数据库访问模式。

2.3 一套压测必看的监控指标清单

监控没铺好就开始压测,等于闭着眼睛开车。我压测时一定会同时盯住三个层面的指标:应用层、JVM层、基础设施层。

监控层面关键指标为什么要看
应用层QPS、RT(均值/P99/P999)、错误率这些是结果的直接呈现,P99比均值更敏感地反映用户体验
JVM层GC频率与耗时、堆内存使用趋势、线程数高并发下GC停顿会造成RT尖刺;内存缓慢增长预兆泄漏
基础设施层CPU、内存、磁盘IO、网络带宽、连接数任何一项资源被打满,都可能成为系统真实的瓶颈点

工具选型上,我的标配组合是:Prometheus + Grafana采集系统指标,Arthas做线上诊断,JFR(Java Flight Recorder)做深度JVM剖析,async-profiler生成火焰图。压测开始前先确认这些工具的采集链路是通的,不然压测中间发现问题要定位时,才发现监控数据没记录下来,那就白跑了。

3. 压测执行节奏:从单机探底到容量极限

压测不是直接把并发线程数拉到5000看系统崩不崩,那样得到的信息很有限。我习惯分三个阶段走,每个阶段解决一个具体问题。

3.1 基准测试:先把系统的"底"摸清

第一阶段是基准测试,目标是确定系统的单线程(或低并发)性能上限。我用JMeter先设10个线程跑一轮,观察每个请求的耗时和吞吐。这一步的作用是排除网络和环境干扰,看清一个请求从进入到返回,应用自身到底耗费了多长时间。

基准测试里我会特别关注RT的构成拆分。如果发现一个接口平均耗时800ms,而其中业务逻辑只占50ms、缓存读取占200ms、数据库查询占550ms,那么后续压测的重点就非常清晰了——数据库访问是大头。这时候就算把应用QPS压到很高,数据库一饱和整个链路立刻崩。相反,如果业务逻辑本身消耗400ms,就该先从代码优化入手,而不是盲目调大连接池。

3.2 梯度加压:找到临界点而不是模糊的"上限"

第二阶段做负载测试,用阶梯式加压方式找到系统的临界点。我的做法是:从100并发开始,每轮递增50并发,每轮持续3分钟,观察TPS和RT的变化曲线。记录下两件事:

  • TPS停止线性增长的那个点。在并发数到达某个值之前,TPS应该随着并发数近似线性上升;一旦上升速度放缓或不再上升,说明某个资源开始饱和。
  • RT开始急剧上升的那个点。这是系统进入排队状态的信号,比TPS拐点更灵敏。很多系统在TPS还没下降时,RT已经翻了几倍,用户体验已经变差了。

这两条曲线交叉的区域,往往就是系统的最佳工作区间。我会用这个区间的数据去推算容量评估,比如"在可接受的P99 RT内,这个服务最多支撑多少QPS"。

3.3 稳定性测试:长时间运行才暴露的GC和内存问题

第三阶段是做稳定性测试,我用能用到的最大资源跑4到8小时,重点盯着几个趋势曲线:堆内存是平稳波动还是一路爬升?Young GC频率有没有越来越快?Full GC有没有突然出现?

为什么必须跑这么久?因为很多问题是积少成多的。比如一个接口每次请求都会创建一个缓存对象但解引用不彻底,在低并发下这个对象很快被GC回收,看不出异常;但在高并发下对象创建速率大于回收速率,堆内存就会螺旋上升,几个小时后触发Full GC,RT出现大面积尖刺。这种问题跑10分钟的短压测根本暴露不了。

稳定性测试的判定标准我一般定三条:错误率为0(或可接受阈值内)、P99 RT波动幅度不超过正常均值的20%、内存曲线没有持续上升趋势。三条全过才认为系统通过了稳定性验证。

4. 问题定位的完整链路:CPU、内存、线程、数据库四类典型瓶颈

压测中最有价值的环节,其实是问题定位。这一节我会分别拆解四类典型瓶颈的标准排查链路,再用一个综合案例把整条思路串起来。

4.1 CPU吃满但QPS上不去:用火焰图找出热点代码

CPU问题的典型特征非常直观:top命令里看到应用进程CPU占用已经90%甚至100%,但压测TPS还是上不去,RT还在上涨。这说明CPU已经饱和,请求在处理请求的计算上耗尽了所有时间片。

我的标准排查链路是:

  1. 定位进程和线程。用top -Hp <pid>找到CPU占用最高的线程PID,把这个PID记下来。
  2. 把线程PID转成十六进制。JVM的线程栈里使用的是十六进制线程号,执行printf '%x' <pid>转换。
  3. 导出线程栈。执行jstack <pid> > thread_dump.log,在dump文件里搜索刚才转换出的十六进制线程号,定位到具体线程。
  4. 解读线程状态。如果看到多个线程都卡在同一个业务的代码行上,说明这里是CPU热点;如果线程一直处于RUNNABLE且循环执行某个JSON序列化或正则匹配,那大概率这段逻辑走的是性能很差的实现。

更进一步,我推荐用async-profiler生成CPU火焰图。它的优势是不需要在代码里埋点,直接采样Native栈和Java栈,一眼就能看出哪个方法在最宽的调用栈上。火焰图的宽度就是CPU时间的占比,宽度越宽、层叠越深的方法,越值得优先优化。

我遇到过最典型的例子:一个参数校验方法里用了大量正则表达式匹配邮箱和手机号,平时流量小感觉不到,压测到500 QPS时CPU直接飙到90%。火焰图显示Pattern.matcher占了将近一半的CPU时间。替换成显式的字符遍历校验后,CPU直接降了35%,TPS翻了一倍。正则匹配是CPU热点的高频元凶,遇到CPU问题先查正则。

4.2 频繁Full GC和OOM:用JVM监控和堆快照揪出元凶

GC问题在压测中几乎必然会遇到,但关键要区分正常的对象分配回收和异常的堆内存膨胀。

我的定位链路是:

  1. 先看GC趋势。压测期间用jstat -gcutil <pid> 1000 1000每秒采样一次,观察FGCT(Full GC耗时)、FGC(Full GC次数)、S0/S1/E(各区使用率)。如果Full GC次数快速上升,且每次耗时都超过几百毫秒,说明堆内存快被占满了,对象在持续堆积。
  2. 导出堆快照。执行jmap -dump:format=b,file=heap.hprof <pid>,把当前堆内存快照导出来。注意在OOM还没发生时就要抢时间导出,等OOM之后再导往往进程已经没了。
  3. 用MAT或JProfiler分析。打开堆快照后,第一眼看Dominator Tree(支配树)里最大的几个对象是谁,再看Leak Suspects报告,它会自动帮你圈出疑似泄漏的根路径。

有一个实战案例很典型:某服务压测时堆内存持续爬升,Full GC频率从每小时2次变成每10分钟1次。MAT分析发现,一个ThreadLocal里存的用户上下文对象占据了堆内存的40%。追问根源是业务代码在请求处理完没有调用remove()清理ThreadLocal,而应用用的是线程池,线程复用导致每个线程都吸附了一个上下文对象不释放。修复后加了一行finally { threadLocal.remove(); },堆内存曲线从上升变成平稳,Full GC直接消失了。

注意:不要一看到GC问题就调堆大小参数。堆调大只是把溢出的时间拉后,不解决对象累积的根因。先找到谁在持有对象,再决定怎么改代码。

4.3 线程大量阻塞:连接池和锁竞争的排查方法

线程阻塞的典型现象是:TPS上不去,但CPU利用率不高,线程dump里的线程大多处于WAITING或BLOCKED状态。这说明请求没有在被计算,而是在排队等资源。

排查方式主要靠线程转储分析:

  1. 先抓现场。压测中连续执行2~3次jstack命令,间隔5秒,得到的dump文件对比分析。同一线程多次出现在相同位置,说明它卡死在那里了。
  2. 用Arthas快速定位。如果环境允许,我强烈推荐用Arthas的thread命令。执行thread -n 3能看到CPU占用最高的前3个线程;执行thread -b直接定位当前被阻塞的线程正在等哪把锁、锁被哪个线程持有。这是最省力的一步。
  3. 区分两类阻塞场景:
  • 锁竞争:线程栈里能看到大量线程卡在synchronized或Lock的获取处。需要分析锁的持有时间为什么这么长,临界区里有没有做耗时操作(比如IO、远程调用、大循环)。解决思路是缩小锁粒度、改用ReadWriteLock或ConcurrentHashMap等无锁结构。
  • 连接池等待:线程卡在getConnection方法附近,说明数据库或Redis连接池已经空了。这时不只是看应用日志,还要去查数据库端的max_connections和当前活跃连接数,确认瓶颈到底在连接池配置还是数据库本身的容量。

连接池等待是我在压测里遇到最多的一种线程阻塞。很多团队把连接池的最大连接数往大了调以为就万事大吉,结果压测时数据库先撑不住,反过来拖垮应用。连接池的大小不是越大越好,要结合数据库的最大连接数和业务的单个请求占用连接时长来综合定。

4.4 数据库扛不住:从慢SQL到连接数耗尽

数据库问题在压测中的表现往往比应用自身更先暴露,因为几乎每个业务接口都依赖数据库。排查我却建议先从数据库端入手,而不是在应用端折腾。

  1. 开启慢查询日志,压测结束后把压测时段的慢SQL捞出来,按执行次数和耗时排序。
  2. 对慢SQL跑执行计划,用EXPLAIN看有没有走索引、扫描行数是不是爆炸。我见过最多的"压测才暴露"的问题是:查询条件里的字段没有索引,或者查询用LIKE '%xx'导致索引失效,小数据量测试时根本看不出来,数据量大压测时立刻变成全表扫描。
  3. 看数据库连接数。应用侧连接池打满,数据库侧同样能看到大量连接堆积。用SHOW PROCESSLIST可以看到当前有哪些SQL在跑、跑了多久、锁等待多不多。如果发现大量Waiting for table metadata lock,说明有人在做DDL操作没提交,把业务查询堵死了。

这里有一个容易被忽略的经验:数据库性能问题的根源,很多时候不在数据库,而在应用怎么用数据库。比如一次请求里发了N次SQL查询而没有做批量处理,或者先查一次再用结果循环查了N次(N+1问题)。压测前用Arthas的trace命令追踪一下数据库调用次数,比直接优化SQL性价比更高。

4.5 一个综合案例:压测时TPS只有预期一半,问题在哪

最后用一个我印象很深的案例,把上面的方法串成一条完整的排查链路。

当时我们压一个订单查询接口,压测目标是单机800 QPS,但实测跑到420 QPS左右就再也上不去了,错误率开始升高,RT从平均60ms飙到600ms。第一反应是代码有问题,于是按流程走:

  • top一看,应用进程CPU只有35%,不像CPU瓶颈。
  • jstack看线程栈,发现大量线程阻塞在getConnection等待数据库连接。
  • 查连接池配置,最大连接数64,业务单个请求正常执行只要50ms,理论上不该打满。
  • 再看数据库端SHOW PROCESSLIST,发现几百个慢SQL在跑,每条耗时1~2秒。

关键转折点在那几条慢SQL上。正常业务查询不应该这么慢,单独执行发现单条SQL很快,但在压测并发场景下却变得异常慢。再看执行计划,查询条件里的created_at字段有索引,但压测脚本的查询时间范围是全量数据,命中数据量太大,MySQL选择了全表扫描——这是索引选择策略和统计数据在数据量临界点时的误判,单测场景根本触发不了。

我们做了两步修复:第一步,给SQL加了FORCE INDEX强制走索引,并把查询范围按业务实际情况做了限制;第二步,在查询语句前直接优化了执行计划使用机制,让MySQL的统计信息重新校准。优化后同样的压测方案,TPS直接到了850 QPS,P99 RT从600ms降到80ms。

这个案例给我的最大启发是:瓶颈定位不要停留在"看CPU、看内存"的面上,线程栈指向的地方只是症状表现,真正的根因往往在第三层第四层。

5. 优化不是玄学:从代码热点到架构取舍的落地顺序

定位到问题之后,优化要讲方法论。很多团队喜欢凭感觉改代码,或者一上来就谈加机器、上缓存,这都是本末倒置。我的优化顺序永远是从最廉价的代码级优化,一步步走到架构级改造。

5.1 先别急着优化:让火焰图决定改哪里

优化和排查之间,有一道分界线:排查是找"哪里有问题",优化是改"影响最大"的地方。性能优化的一个铁律是二八法则——80%的瓶颈集中在20%的代码路径上。如果不看火焰图就动手改业务代码,很可能浪费几天时间优化了一段根本不热门的代码,真正的热点还在原地。

我拿到火焰图后的判断标准很简单:从最宽的调用栈顶往下找,看有没有可以用更廉价方式替代的操作。常见的热点从高到低排序:序列化/反序列化、正则匹配、日志输出、集合排序、反射调用、字符串拼接。这些都是"隐形开销",单次执行毫秒级不到,但在高并发下会被无限放大。

5.2 代码层:消除热点路径上的"隐形开销"

代码级优化是性价比最高的手段,不需要动架构,改完立即见效。我总结几个高频场景:

  • 减少重复计算:循环里如果有对同一结果的重复获取,提到循环外。看似简单,我在真实代码里见过不下二十次。
  • 避免无意义的对象创建:每次请求都new一个很大的临时对象,如果这个对象可以复用,就改成池化或静态字段。这个改动直接影响GC压力,比调JVM参数有效得多。
  • 用批量操作替代循环单次操作:for循环里逐条update数据库,改成一条batch update,网络IO次数从N次降到1次,效果立竿见影。
  • 字符串拼接别用+:在循环里用StringBuilder,避免产生大量中间字符串对象。这在JDK 8以前是绝对有效的手段,在JDK 9+编译器做了优化后效果差一些,但循环内拼接仍然建议用StringBuilder。

代码级优化有一个原则我要强调:每次只改一处,改完立刻重新压测。不要一次改十个点,改完不知道是哪个点起了作用。性能优化本质上是个AB实验,每一处改动都应该有单独的验证结果。

5.3 并发层:线程池参数、锁粒度与无锁化

并发层面的优化,核心是让资源利用率上来的同时不制造新的竞争热点。我通常会从线程池和锁两个方向入手。

线程池参数不是配置一个corePoolSize和maxPoolSize就完事的。我压测时会看线程池的队列积压情况——如果队列持续增长,说明线程数不够;如果线程长期空闲而QPS没上来,说明线程数多了或者瓶颈在别处。一个我实践下来比较稳妥的调参思路是:先估出单个线程能处理的QPS基数,再用目标QPS除以基数得到一个初始线程数,再在压测中微调。线程不是越多越好,过多线程反而会增加上下文切换开销,CPU使用率上去了QPS却不涨,就是过配置的信号。

锁的优化优先级从高到低我排为:无锁 > 读写锁 > 分段锁 > 最小粒度同步块。能用ConcurrentHashMap就不上synchronized大锁;能拆细锁就不锁整个方法;能读多写少就用ReadWriteLock。最常见的反例是:一个高频的读方法,为了一个不常变的配置更新,整个方法都加了synchronized,压测时读请求全部串行化,QPS直接掉一个数量级。

5.4 数据访问层:减少行数、命中索引、批量化

数据库优化的核心目标是让数据库做更少的工作,而不是让数据库更强壮。数据访问层我按三个步骤操作:

  1. 只取需要的字段和行数。SELECT *在高并发场景下是灾难,多余的字段传输浪费网络IO,多出的行数放大数据库扫描成本。改成明确字段、加上LIMIT,效果最直接。
  2. 让索引真正命中。压测后把所有慢日志里的SQL拿一遍,逐一检查EXPLAIN的type字段。ALL(全表扫描)是红线,ref、range、eq_ref都算可接受,const是最理想。组合索引的字段顺序要和查询条件匹配,注意最左前缀原则。这里有一个常见的冤枉:索引本身没问题,但函数包裹了索引列,比如WHERE DATE(create_time) = '2024-01-01',这时候索引完全失效,改成区间查询create_time BETWEEN '2024-01-01 00:00:00' AND '2024-01-01 23:59:59'就恢复正常了。
  3. 批量化数据库访问。压测场景下最怕"循环单查"。把N次单条查询合并成一次IN查询,把N次单条更新合并成batch update,这种改动对数据库的连接占用和IO次数下降是数量级的优化。

5.5 架构层:缓存分层与异步化的边界

走到架构层做优化,说明代码层和并发层的余地已经不大了。但架构改造动辄涉及多个服务、中间件的协同改动,我建议遵循一个原则:先做风险最低、收益最明确的一步,不要一上来就搞大重构。

  • 缓存分层:本地缓存(Caffeine)只解决单机节点的热数据访问,Redis缓存解决集群共享的数据访问。到底用哪一层,取决于数据的变更频率和对一致性的容忍度。变更频繁的数据如果硬塞缓存,反而会因为不断失效和回源引入新的延迟。我见过最成功的缓存优化,是把一个"查详情且很少变更"的接口加了一层Caffeine本地缓存,命中率80%以上,数据库负载直接降了60%。
  • 异步化:不是所有请求都必须同步返回结果。对于发送通知、记录日志、更新统计这类非核心操作,可以用消息队列或者线程池异步执行,把同步等待的时间从请求链路里摘掉。异步化的边界在于:业务能否容忍最终一致。如果不能,强行异步化只会引入数据不一致的线上事故。

架构级优化的通用验证方法是:改完后压测对比同一个指标的基线和优化后数值,比如TPS提升比例、P99 RT下降比例、数据库连接数峰值下降比例。这些数据会实际告诉你,架构改动到底值不值得。

6. 把压测成果沉淀成团队资产:性能基线与回归机制

压测做完、问题优化完,还不算结束。如果压测结果只存在某个人本地的报告里,下一次项目上线前又要从零开始,那就太浪费了。我习惯把压测的产出变成团队可复用的资产。

6.1 建立性能基线档案

每次压测结束,我会把以下几项整理成一个脑图结构存的文档:

  • 本次压测的环境规格(机器配置、JDK版本、关键组件版本)
  • 压测脚本和参数(并发数、持续时长、数据准备情况)
  • 关键性能指标(TPS、P99 RT、错误率、GC情况、CPU峰值)
  • 瓶颈定位过程(发现的问题、定位方法、根因分析)
  • 优化方案与验证结果(每一处改动对应的前后数据对比)

这份档案的意义在于:团队迭代三个月后,再有人问"这个接口现在能扛多少QPS",不用重新压一遍,直接查档案就有答案。同时,下次再压测时,用同一套基线数据做对比,可以快速判断性能是变好了还是劣化了。

6.2 让压测进入日常研发流程

我在实践中最有效的做法,是把性能压测接入到CI/CD流程里,每次上线前跑一次轻量级的性能回归测试。这里的轻量级是关键词——完整压测的成本太高,不适合每次都跑,但可以用一个小时跑一个固定的基准场景,对比基线的QPS和RT。

只要新代码导致关键接口的TPS下降超过10%,或者P99 RT上涨超过15%,就阻断合并,要求开发者先自查。这个机制在团队初期推行时会有点阻力,但坚持跑两三个迭代后,大家就会形成"写代码时顺手注意性能"的习惯,因为没人想在上线前被压测脚本卡住。我最明显的感受是:这个门槛设立了半年后,线上因为性能劣化导致的事故率下降了八成。

6.3 我个人踩过最深的坑和实验心得

最后分享几个只有实际跑压测才会踩到的细节。先说深坑:压测工具的瓶颈也可能是系统瓶颈。早期我们用JMeter打流量时,发现目标服务的CPU还没怎么动,TPS就上不去了。排查完才发现JMeter所在的施压机自己先扛不住了——线程数开太多,压测工具本身的线程调度成了瓶颈。后来施压机改用多台部署,或用wrk这类更轻量的工具,才把问题澄清。

再说一个原则:压测前一定先把压测脚本在小流量下自测几遍。我有一次用JMeter跑压测脚本,结果全链路报401,排查发现是鉴权Token没处理好。这个错误浪费了我整个上午。后来我在脚本里单独加了一个"冒烟测试"步骤:先小并发验证接口返回正常,再上大并发正式压测,这个习惯帮我省下大量白跑的时间。

最后关于心态,压测这件事,不要追求一次压测就把所有问题都找出来。每一次压测的目标可以很小——这次只看某个接口的极限TPS,下次只看稳定性下的内存趋势。性能优化本身是个持续迭代的过程,每轮压测发现并解决一两个真问题,比一次性做完所有检查更有现实价值。压测报告不是你给领导的PPT,而是你下一次优化时最有用的起点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询