性能测试、负载测试、压力测试的全面解析
做开发、做测试、做运维的朋友应该都有过这种经历:领导或者客户问“系统上线前做过压力测试吗?负载能扛多少并发?”,结果查报告的时候发现,大家嘴里说的“压测”其实五花八门,有的只是拿接口工具单线程刷了几遍,有的用脚本模拟了200个用户跑了一晚上,还有的干脆拿FurMark把笔记本显卡烤了十来分钟就汇报“稳定性达标”。
这里有个很现实的问题:性能测试、负载测试、压力测试这三个词在很多人眼里几乎是同一个东西,但在实际工程里,它们的目标、方法和结论完全不是一回事。如果连测什么都说不清楚,后面的测试数据、调优方向、上线决策就全站不住脚。这篇文章我结合自己这些年做压测、扛线上事故、帮人“擦屁股”的经验,把这三种测试完整拆一遍,再把JMeter、LoadRunner、FurMark这些工具怎么用、线程组怎么选、指标怎么判、上线前到底该测哪些东西,一次性讲明白。
适合谁来读呢?刚接触性能测试的测试工程师、准备把自己的接口或小程序搞上线但又没正经做压测的开发者,以及每次开会都被问“性能怎么样”但答不上来的运维同学。读完之后,你至少能分清楚自己到底该做哪一类测试,以及怎么把这套事从头到尾落地。
1. 先搞清楚:性能测试、负载测试、压力测试到底是什么
很多人会把这三个测试当成同一个东西,是因为它们都涉及“给系统加压、看表现”这个动作。但真正做的时候,它们的测试目标、场景设计、结果评价都是不一样的。我平时给别人讲这三个概念,喜欢用电梯来打比方。
1.1 三个概念的正确定义
性能测试(Performance Testing),关注的是“系统在某个既定负载下的表现是否符合预期”。还是用电梯说事:一栋写字楼规定高峰期电梯往返一趟不能超过2分钟,那我就在早高峰时段测一测,20个人乘坐的情况下,电梯实际花了多久、停了多少层、开关门是否正常。这里的核心是“验证性能需求是否被满足”,负载是给定的、明确的。
负载测试(Load Testing),关注的是“系统在预期负载范围内的表现如何变化”。比如这栋电梯,设计标准是载1000公斤,我依次测试10人、20人、30人、40人的情况,看响应时间、故障率怎么变化,找出它在接近额定负载时的表现。负载测试的核心是模拟真实工作负载,验证系统在正常或稍高负载下的行为,包括并发上升过程中的资源消耗趋势。
压力测试(Stress Testing),关注的是“系统超出额定负载后会怎样,极限点在哪里”。还是那个电梯,我往里塞60人、80人、100人,超载报警什么时候响、钢丝绳会不会崩、停止后重新启动还能不能恢复正常。压力测试的核心是找到系统的崩溃边界,验证它在极端情况下的抗压能力和恢复能力。
1.2 三个概念的关系与区别
从目标上看,三者其实是递进的:
- 性能测试:回答“够不够快”,基准校验
- 负载测试:回答“人多的时候还正不正常”,容量验证
- 压力测试:回答“到底能承受多少人,超了会不会崩”,边界探索
我用一个实际接口测试的例子来解释。假设一个登录接口,要求是500并发下平均响应时间低于200ms,错误率低于0.1%。
性能测试的场景是:直接打500并发,跑10分钟,看平均响应时间、P95、错误率是否达标。这里关注的是“是否符合指标”。
负载测试的场景是:从100并发开始,每5分钟增加100,一直加到800,观察响应时间曲线在哪里开始拐弯、吞吐量在哪里停止增长。这里关注的是“容量还能不能增长”。
压力测试的场景是:直接上1000、1500、2000并发,直到接口超时率飙升、服务重启或连接池打满,再观察停止施压后服务是否自动恢复、需不需要人工介入。这里关注的是“崩溃边界在哪、恢复能力如何”。
1.3 为什么这三者经常被混为一谈
混为一谈的主要原因有三个。
第一个原因,是工具层面高度重叠。JMeter的线程组既能做性能测试、负载测试,也能做压力测试,差别只在于配置的参数不同,导致不少人以为“用JMeter跑了就是压测了”。
第二个原因是行业术语的不严谨。面试时写“精通性能测试”,实际上简历上列的都是压力测试的经验;领导说“做个体检”,指的是性能测试,但执行的人理解成了压测系统直到崩溃。这种信息损耗在工作中非常常见。
第三个原因是结果呈现被简化。很多公司最终只关心“能扛多少并发”这一个数字,导致负载测试和压力测试的区分在结果汇报时被彻底抹平。
这带来的后果是:该做性能测试验证指标时,直接用压力测试把系统搞崩了,最后得出的结论只有“系统太脆弱”,没有具体的性能基线数据;反过来,该做压力测试探边界时,只做了低负载的性能测试,上线后流量一冲,直接雪崩,没人知道系统真实的极限在哪里。
所以我一直建议,一个完整的压测体系里,性能测试、负载测试、压力测试都要做,它们互相补充,缺一个都不完整。性能测试负责证明系统达标,负载测试负责摸清容量发展规律,压力测试负责给我们兜底和预警。
2. 核心性能指标解析:看懂这些才能正确下结论
不管用哪种工具,最后落地的都是数据。但很多同学测完只会看一个“平均响应时间”,这其实远远不够。我把主流的性能测试指标按类别拆开讲,讲清楚每个指标的意义、坑点和判断逻辑。
2.1 响应时间与百分位:平均值的骗局
响应时间(Response Time)是最直观的指标。但平均响应时间是个很容易骗人的数字,因为少量极慢的请求会把平均值拉高,或者大量快速请求把少数慢请求掩盖掉。
我举个真实例子。某个接口测了1000次,其中900次耗时80ms,100次耗时800ms,平均值算一下是152ms,表面看挺好。但实际体验是:10%的用户等了近1秒,这批用户早就流失了。所以只看平均值完全不够,必须看百分位。
百分位响应时间的含义是:假设P95 = 300ms,意思是95%的请求响应时间小于等于300ms,剩余的5%是慢请求。一般建议关注P90、P95、P99三个档位。P99能暴露那些在峰值时被挤爆的请求,尤其在银行支付、电商下单这类场景,P99就是真实用户体验的下限。
我自己的习惯是:
- P50:中位数,看整体水平
- P90:看一般卡顿情况
- P95:看体感变差的临界
- P99:看最差那批请求是否严重超时
如果P50很低但P99很高,说明系统在某些情况下出现了明显抖动,可能是GC停顿、线程池排队、数据库慢查询命中,也可能是限流器触发后人为等待。这时候就要配合后端日志和服务链路追踪去定位。
2.2 吞吐量(TPS/QPS)与并发用户数的关系
吞吐量在互联网场景下最常用的是TPS(每秒事务数)和QPS(每秒查询数)。两者细微区别在于,QPS偏查询接口,TPS偏包含多步操作的业务事务。JMeter中有一个聚合报告里的“Throughput”字段,负载测试中最核心的判断依据就是它。
这里要提一个重要关系:吞吐量和并发用户数不是线性增长的。随着并发上升,吞吐量通常会经历三个阶段:线性增长期、增长放缓期、瓶颈期甚至下降期。
线性增长期:系统资源充足,业务处理路径顺畅,并发每涨一倍,吞吐量基本也翻倍。
增长放缓期:某个资源开始接近饱和,比如数据库连接池到了上限,或某个后端接口达到了自身瓶颈,此时并发增加,吞吐量增速下降,响应时间开始拉长。
瓶颈期:吞吐量基本不再增长,甚至略有下降,响应时间剧烈恶化,错误率开始抬头。
这个拐点位置的价值极高。找到它,你也就找到了系统的容量上限。这也是负载测试的意义所在。
并发用户数这个指标也要单独提醒。很多测试人员把JMeter线程数直接当成“在线用户数”,这是不对的。JMeter线程数是虚拟并发数,代表同一时刻发出的活跃请求数,而在线用户数是登录系统但可能大部分时间在浏览、思考的用户,实际同时发请求的并发数可能只有在线人数的5%到20%。设计场景时,不能把在线人数直接塞进线程组,要根据业务行为的活跃比例来折算。
2.3 错误率与资源利用率
错误率是硬性指标,通常要求低于0.1%。如果压力测试过程中错误率从0突然飙升,往往意味着系统已经进入过载状态或某类资源耗尽。JMeter聚合报告中的Error %是一个重要的截停信号,我压测时遇到错误率上升会先关注响应时间是否同步恶化,如果两者都恶化,基本判定系统到达瓶颈。
但要注意错误率和响应时间不一定同步变化。有的是响应时间先拉长,错误率后续才上升;有的是连接池被快速打满,错误立刻爆发。所以执行压测时,应该同时监控错误率的变化趋势,不要等到压完了才看。
资源利用率包括CPU、内存、磁盘I/O、网络带宽,压测过程中需要分层监控。CPU看的是内核态和用户态的占比,如果用户态很高,说明业务计算密集;如果内核态很高,可能是上下文切换频繁;如果还有大量软中断,则要考虑网卡多队列、中断绑核的问题。内存观察的是不光是空闲内存,还要看cache/buffer、swap的使用情况。磁盘和网络也需要关注,大量的慢SQL会在数据库服务器上体现为磁盘I/O等待升高,带宽打满则会导致响应时间普遍增加。
2.4 不同业务场景的指标关注重点
没有一套指标能通吃所有业务。我根据实际业务场景大概分了以下四类:
第一类是面向C端用户的接口,比如小程序登录、首页信息加载。这类场景最看重响应时间的中位数和P95,因为直接决定用户体验。吞吐量反而不是首要关注点,因为C端并发高峰通常有限,更怕的是单个请求太慢导致用户流失。
第二类是面向B端系统的批量接口,比如对账、批量消息推送。这类场景吞吐量是第一优先级,响应时间可以放宽到秒级甚至几十秒,只要不超时失败即可。
第三类是后台任务系统,比如定时任务、定时报表生成。这类系统往往在固定时间点集中跑批,压力测试重点要验证的是在极限并发执行时是否会互相争抢资源,是否导致数据不一致。
第四类是视频、文件类业务,比如直播转推流、文件上传下载。这类场景带宽和I/O才是核心约束,CPU和内存反而次要,压测的重点要放在网络吞吐和磁盘顺序写入能力上。
你上手一个系统时,第一件事不是调工具,而是先确认业务目标,把指标权重定好,否则测出来的数据没有参考意义。
3. 工具选型:JMeter、LoadRunner,以及CPU和显卡压测的专用工具
性能测试工具非常多,每家的侧重点不一样。我按使用场景和热度,把目前最常用的几类工具拆开聊聊,方便你按需选择。
3.1 JMeter:开源界的扛把子
JMeter应该是目前使用率最高的开源压测工具了,纯Java开发,基于线程模型模拟并发。它最大的优势是开源免费、插件生态丰富、协议支持广泛。做HTTP接口压测是它的看家本领,同时它还支持JDBC、JMS、FTP、TCP、WebService等多种协议。
我自己用JMeter的体会是,它做接口级性能测试、负载测试、压力测试完全够用,而且配合插件后在场景控制上非常顺手。但它的缺点也很明显:默认线程组在长稳测试和复杂阶梯加压场景下不够灵活,需要装第三方插件;脚本录制功能相对简陋,复杂业务流程可能需要纯手写逻辑;GUI模式压测本身会有性能损耗,一定要用命令行模式执行。
JMeter性能测试步骤,我后面单独用一个章节详细写,这里先提一下基本流程:创建测试计划、配置线程组、添加取样器(如HTTP Request)、加监听器、设置CSV参数化、命令行执行、解析报告。这套流程是一个合格测试工程师必须烂熟于心的。
3.2 LoadRunner:企业级重型武器
LoadRunner(也叫LR)是Micro Focus(以前是HP)家的商业性能测试平台,典型的重型武器。它由三大部件组成:VuGen录制编写脚本、Controller设计运行场景、Analysis分析结果。LoadRunner性能测试步骤一般是这样:在VuGen中录制脚本并定义事务、Run-Time Settings中设置思考时间与迭代策略、Controller中设置虚拟用户数和加压方式、执行测试同时开启系统监控,最后用Analysis生成报告。
LoadRunner的优势在于支持的协议非常多,尤其是基于Windows的企业应用(比如SAP、Citrix、Oracle Forms)和传统CS架构系统,JMeter很难替代。它的分析报告也很专业,能自动定位瓶颈、生成图表。但缺点同样明显:License费用极高,动辄几十万,非大厂很难承受;安装部署比较重,尤其是新版对操作系统和中间件有严格限制;学习和维护成本高。
我建议如果你的团队是中大型企业、系统结构复杂、要求全链路专业监控,可以选LoadRunner;如果团队预算有限、系统以Web和API为主,JMeter完全够用。很多公司实际上也是JMeter为主、LoadRunner少数场景配合使用。
3.3 CPU压力测试工具
CPU压力测试和软件性能测试是两种思路。软件测试关注业务接口的并发能力,CPU压力测试则关注硬件在极限计算负载下的稳定性和散热能力。自己组装工作站或者给服务器做硬件验证时,这类工具比较常用。
Linux环境下最经典的是stress和stress-ng。简单命令示例:
# 启动4个stress进程跑满4个CPU核心,持续60秒 stress --cpu 4 --timeout 60s # stress-ng指定具体压测算法 stress-ng --cpu 4 --cpu-method matrixprod --timeout 60s我自己的经验是:stress-ng比stress功能丰富很多,它不只可以压CPU,还能压内存、磁盘、网络、进程数,适合做全面硬件稳定性测试。运行stress-ng的时候,建议配合顶层监控工具,比如top、htop、mpstat,观察每个核心的使用率和温度。
另外,压力测试过程中还要重点观察CPU的频率和温度。正常跑压力状态时,CPU频率应该保持在高位,温度根据散热系统不同会稳定在某个区间。如果温度撞墙到一百度上下,频率大概率会降下来,就说明散热有问题了,这时候压测结果的实际意义也就不大了。
Windows平台下,常用工具是IntelBurnTest、Prime95、AIDA64系统稳定性测试。这些工具基本都会把CPU指令集跑到极限,让处理器短时间内全负荷运转,是判断CPU是否稳定、散热是否达标的实用手段。
3.4 显卡压力测试工具FurMark
显卡压力测试和CPU类似,核心也是验证硬件在高负载下的稳定性和散热。这里最出名的工具就是FurMark,它的界面里那只毛茸茸的甜甜圈几乎成了硬件圈“烤机”的代名词。
FurMark的工作原理是调用GPU渲染复杂的毛发特效,把显卡的着色器、显存、供电全部推到高负载状态。常见的帧数监测工具包括MSI Afterburner、GPU-Z,可以同时监控GPU温度、显存温度、核心频率、显存频率、风扇转速、功耗等数据。
我来说一下实操细节:跑FurMark之前先看显卡默认温度和风扇曲线,然后选择预设分辨率,1080P或者2K都行,建议开8倍抗锯齿或者不开,看你要验证什么负载。分辨率越高、抗锯齿越高,GPU负载越重。一般烤机20到30分钟,温度曲线平稳不持续上升、画面不花屏不闪退、风扇转速正常,就算是通过。烤机时间太短没有意义,因为温度传导到散热器和机箱内部需要时间,前几分钟并不能代表稳态温度。
FurMark跑的时候,你会看到GPU功耗直接拉满,如果电源功率不足或供电设计有问题,会直接黑屏重启,这也是一种验证方式。但我要提醒一点:新显卡跑FurMark时,如果不想损坏硬件,就提前关注温度曲线,一旦发现温度异常升高或频率骤降,说明散热压不住,建议立刻停止烤机并检查机箱风道。
4. 完整的压力测试实操流程(以JMeter为例)
做压力测试前,很多人一上来就打开JMeter、添加线程组、填个1000并发,然后跑,发现系统崩了,却说不清是哪个环节崩的。这其实是不科学的方法。我现在把一套完整可复用的流程拆出来,搭配热词“jmeter性能测试步骤”和“压力测试选择什么线程组”一起讲。
4.1 压测前的准备
压测开始前有四件事必须确认清楚。
第一,明确压测目标。这次是测单个接口的极限并发,还是测某个核心链路的容量,还是验证系统的恢复能力?目标决定了线程组、持续时间和结果分析方式。
第二,准备独立的压测环境。最理想的是独立测试环境,配置尽量与生产一致。如果只能在生产环境压测,就必须在低峰期进行,并且要设置熔断和降级预案。如果压根没有独立环境,那至少保证压测数据写入的库和缓存与线上隔离,避免脏数据污染正式数据。
第三,铺底数据。数据库要准备足够的数据量,比如一个列表接口,如果表里只有几百条数据,压出来的接口性能毫无参考价值。生产环境可能有几百万条,而测试环境的库只有几千条,结果差异巨大。
第四,监控工具先行。至少要有基础监控:服务器CPU、内存、磁盘、网络,数据库连接数、慢查询数,以及应用本身的JVM内存、GC日志。没有监控的压测,等于闭着眼睛开车,出了问题只能猜。
4.2 线程组怎么选
JMeter里线程组的选择直接决定了压力模型,这是新手比较容易困惑的地方。
默认的Thread Group很“直白”,可以设置线程数、Ramp-up时间、循环次数,适合简单的性能测试和固定负载的压测,但它的加压方式比较粗,比如设置“线程数=1000,Ramp-up=60秒”,大致是每秒增加16到17个线程,但是具体的增量曲线不够平滑,也不方便做阶梯式加压、阶段维持。
如果你做的是压力测试,我更推荐使用Ultimate Thread Group或Concurrency Thread Group。这两个需要通过JMeter插件管理器安装“Custom Thread Groups”插件。
Ultimate Thread Group可以配置多个阶梯,每一行代表一个阶段。比如第一行“100线程,启动时间60秒,持续300秒”,第二行“200线程,启动时间60秒,持续300秒”,这样就能模拟系统逐步加压的过程,每个台阶持续一段时间,直到系统出现拐点或崩溃。这个方式我非常推荐,因为它能清晰看到系统在哪个并发区间开始劣化。
Concurrency Thread Group则更适合做固定并发的持续压力,它的核心参数是Target Concurrency,可以直接从当前并发数开始,保持稳定运行。它的调试更平滑,适合长稳测试和压力恢复测试。
4.3 脚本编写与参数化
写JMeter压测脚本的核心是尽可能模拟真实用户,手段就是参数化和关联。
参数化是防止所有线程都用同一份数据。举个例子,压测一个查询订单接口,如果所有虚拟用户都查同一个订单号,那么数据库大概率会把这个订单号对应的数据页都缓存到内存里,后续所有请求都命中了缓存,测出来的结果会异常好看,但与真实场景完全不符。
实际操作中,我一般用CSV Data Set Config配合外部的用户数据文件。文件里放一批用户ID、商品ID、优惠券ID,线程通过变量引用,按顺序或随机取用。这样每个线程拿到的参数不同,命中不同数据分区,更接近真实情况。
关联则是处理动态参数。比如压测一个需要登录态的业务,登录接口会返回token,后续接口都需要带上这个token。这时需要用JSON提取器或正则表达式提取器,从登录响应中提取token,存到JMeter变量中,然后在后续请求中使用。
如果压测的是登录接口本身,那么就不能用固定账号,否则验证码、单点登录锁定、单用户被多端踢下线等问题都会出现,导致压测数据失真。建议准备一批测试账号,开启账号解锁和验证码旁路开关。
4.4 场景执行与监控
JMeter脚本建议在命令行模式运行,而不是GUI模式。
jmeter -n -t signin.jmx -l result.jtl -e -o report参数解释一下:-n是命令行模式,-t指定测试计划文件,-l输出原始结果文件(JTL格式),-e生成HTML报告,-o指定报告输出目录。这个命令会生成一个非常直观的HTML报告,包含TPS、响应时间分布、错误率等多种图表。
压测执行过程中,监控是多维度的。我习惯开三个窗口同时看:一个窗口跑JMeter命令看终端输出误差,一个窗口看应用服务器的资源状态(top、free、iostat、vmstat),还有一个窗口看中间件和数据库的状态。后端常见的报警指标是连接池活跃数、线程池排队数、GC频率和耗时,一旦这些异常,就要立刻回去查代码或配置。
压测时间也要控制。简单的接口,性能测试跑10到15分钟足够;负载测试建议每级负载稳定5到10分钟;压力测试的极限段,如果系统已经开始报错,再持续压10分钟左右只是为了观察是否雪崩或是否会自动恢复,不建议长时间硬扛。
4.5 结果分析与调优
压测结束之后,最重要的环节是结果分析。先看聚合报告,再结合后端监控数据归因。
这里有个常见的误区:很多人一看到响应时间长了,就急着调代码或加缓存。但压测报告中,响应时间变长的原因可能是多方面的,所以一定要层层下钻。从网络层看是否客户端JMeter本机带宽打满,连接复用是否正常;从应用层看线程池是否排队,GC是否频繁;从数据库层看连接池是否打满,慢SQL是否增多。定位到具体瓶颈后再做针对性优化,否则容易白费力气。
针对常见瓶颈,有一些通用优化手段,我简单列一下,方便参考:
- 如果CPU使用率接近100%,查是否有耗CPU的业务逻辑,比如正则解析、加密算法、大量日志输出,优先优化逻辑和降低重复计算
- 如果内存持续增长,一个可能是JVM堆内存GC不回收,试增加堆内存并优化GC参数;但如果是内存泄漏,那就需要借助堆转储分析工具定位
- 如果线程池排队严重,调大线程池上限,但同时要注意线程切换开销和数据库连接池的承受能力
- 如果数据库慢查询高,开始检查SQL索引、避免全表扫描、考虑加缓存、分库分表等方案
- 如果网络带宽打满,那就压缩接口响应体、升级带宽,或调整传输协议
调优后要重新压测,验证是否改善。性能调优往往是个循环,至少要压三轮:初始基准、调优后复测、最终确认。
5. 小程序上线前要做压力测试吗,还要做什么类似测试
这是一个热度很高的问题,尤其现在很多团队做小程序、H5应用,上线前只知道要做功能测试,却忽略了后续的分类质量验证。我的态度很明确:小程序上线前一定要做压力测试,但这只是其中一环,还要配合其他类型的测试,才能最大程度避免上线事故。
5.1 必须做的测试类型
小程序的测试体系,我一般拆成三个层面:前端客户端测试、服务端接口测试、整体链路测试。
前端客户端测试,重点是小程序本身的功能正确性、启动性能、页面渲染性能、兼容性。小程序不像原生的App那样可以做流畅的启动耗时、内存占用的弱测试,但启动时间、分包大小、首屏渲染时间这些指标是可以在开发者工具里看的。微信开发者工具中有一个“体验评分”,可以检查加载性能、运行性能等,建议发版前跑一遍踩坑。不同机型和微信版本、不同操作系统,都需要在核心机型上过一遍,特别是低端安卓机,前端性能问题往往在那里暴露。
服务端接口测试,这是压力测试的重点。小程序上线前,要针对核心链路做接口级的性能测试。对于小程序来说,登录态校验接口、首页信息接口、列表查询接口、交易下单接口,这四类通常要重点压。因为这些接口承载了绝大多数用户流量,一旦瓶颈挂掉,整个小程序都会出现白屏或操作停滞。服务端接口的响应时间指标,建议P95控制在300ms以内,失败率低于0.1%。
整体链路测试,则是对“小程序 - 网关 - 业务服务 - 数据库/缓存”的完整链路进行压测,验证全链路中间件和基础依赖的容量。很多团队只测了业务服务本身,忽略了网关、Redis、数据库各自独立部署的连接数和超时配置,链路一长问题就多。整体链路压测能真正暴露跨服务调用时的超时和熔断问题。
5.2 小程序压测的注意点
小程序压测和普通Web压测有三个明显的区别。
第一,接口特点不同。小程序接口走HTTPS,还需要带登录态,压测时要先解决token获取和session保持的问题。另外不少小程序接口使用POST + JSON格式,JMeter里需要添加HTTP头管理器,设置Content-Type为application/json。
第二,网络环境的影响更大。用户经常在弱网环境下使用小程序,所以除了常规压力测试,还建议做弱网测试。这可以通过JMeter的模拟网络带宽延迟插件,或者Charles、Fiddler等的弱网设置来做,模拟“3G网络”、“高延迟”、“丢包”的场景,观察接口超时和体验降级是否可接受。
第三,联动业务系统要考虑幂等和防重复。压测下单、支付这类写操作接口时,很可能产生大量真实订单数据或重复请求。建议压测环境单独部署一套测试库,或者在上游接口做好幂等处理。我的经验是,支付相关接口尽量通过沙箱环境来压,避免产生真实资金流水和相关限制。
5.3 上线前的验收清单
我通常给团队一个很朴素但也实用的checklist,上线前照着走一遍:
- [ ] 功能测试已完成,核心流程用例全通过
- [ ] 性能测试覆盖核心接口,P95响应时间在公司约定阈值内
- [ ] 负载测试完成,明确系统容量上限,并确认距离线上预估峰值有至少30%缓冲
- [ ] 压力测试完成,系统在极限并发下不会出现雪崩,能通过限流或熔断自我保护
- [ ] 弱网测试完成,在弱网环境下接口主要错误被捕获、页面有合理提示
- [ ] 兼容性测试完成,核心机型、核心微信版本跑通
- [ ] 安全测试完成,接口无越权、SQL注入等基础问题
- [ ] 监控报警已配置,线上关键指标可观测
这个清单里的每一项具体情况可以根据团队规模调整,但至少要保证“性能测试 + 负载测试 + 压力测试 + 弱网测试 + 兼容性测试 + 安全测试”这个组合不缺席,缺一项,上线后出问题的概率就大一分。
6. 常见问题与排查技巧实录
压测过程中的坑,我踩了太多次了。这里挑一些高频问题,按问题的现象、原因、排查思路整理出来,算是给自己的经验存档,也希望能帮你少走点弯路。
6.1 压测结果不稳定的原因
最典型的现象是同样的脚本、同样的线程数,今天跑数据很好,明天跑数据明显变差,或者连续跑两次结果差异很大。如果没有代码变更,大概率是压测环境存在干扰因素。
排查思路如下:先看压测机本身,也就是JMeter所在机器的CPU、内存、网络是否被其他任务占用。JMeter在压测高并发场景下本身也是消耗资源的,如果一台机器只有2核4G就去跑5000并发,JMeter自己的线程调度就会导致大量延迟,压测结果完全失真。参考经验是,压测机至少4核8G,跑高并发时建议分布式压测,至少准备2到3台施压机,避免客户端成为瓶颈。
再看被测环境是否有定时任务、夜班批量任务在跑。我曾经遇到一个项目,每天晚上10点数据库会自动跑统计任务,于是我晚上10点的压测数据异常恶化。后来把压测时间避开批量任务周期,数据就正常了。
最后检查网络链路。虽然一般都在内网压测,但网络交换机拥塞、防火墙策略调整都可能引入额外的延迟。一般先ping一下目标服务器,确认为分布时延稳定,再进行压测才有可比性。
6.2 服务器CPU飙升到100%怎么办
压测过程中CPU直接打满是很常见的情况,但CPU打满不一定等于系统问题,关键看是哪个进程或哪个线程在消耗CPU。
先用top命令看整体负载,再用top -Hp 进程ID查看具体线程的CPU使用情况,然后通过线程ID去查对应业务日志。如果是Java应用,可以用jstack把线程堆栈导出来,定位是GC线程频繁、业务线程死循环,还是等待锁。
从我的经验看,Java服务CPU飙高,最常见的原因依次是:复杂业务逻辑或正则计算、频繁的序列化和反序列化、JVM频繁Full GC、死循环或空转代码。搞清楚原因后,对应的解法也不同。有的是优化代码,比如用更轻量的序列化方案或缓存正则表达式;有的是调整JVM参数,比如让堆内存更大、选择合适的GC器;有的是排查代码逻辑问题。
这里强调一点:压测时CPU打满,不一定说明程序代码有Bug,也可能是系统本来就该在极限负载下把CPU用满,硬件成本就是为了支撑这种负载的。判断标准还是看业务指标是否达标,响应时间是否还能接受。
6.3 响应时间从哪一级开始分析
响应时间变长,很多人都慌了,有些人直接去看数据库慢日志,有些人去查网关配置。但我的习惯是层析定位,从客户端视角一层层往下追。
使用JMeter压测时,默认的响应时间包括了从发起请求到接收完整响应的时间,这个时间在网络层面包含了连接建立时间、处理时间、响应传输时间。
如果响应时间长了,第一件事是看是不是程序连接服务器这一步变慢。如果Connect Time这个值明显偏高,说明网络或服务器连接队列出现了拥塞,可能是TCP全连接队列满了,也可能是后端应用线程池连接不上。
如果Connect Time正常,但请求处理时间很长,重点排查应用服务器的线程池和GC。如果应用服务器各项指标正常,再看依赖的数据库和缓存。数据库这边重点看连接池是否耗尽、慢SQL数量是否增加、锁等待是否严重。缓存是重点关注是否存在热点键和缓存穿透,大量请求打到数据库。
沿着这条链路逐层拆解,大部分性能瓶颈都能被快速定位。
6.4 压测时容易踩的坑
这里把我踩过的一些高频坑罗列一下,算是提醒。
第一,压测集合时,频繁新建TCP连接,把性能问题测成了连接问题。性能测试过程中应该开启连接复用,让请求复用同一个连接通道,而不是每次建立新的连接,否则测出来的瓶颈可能是连接的开销,而不是业务本身。压测前要看JMeter脚本里的HTTP请求设置,确认连接是否保持复用。
第二,不设思考时间,把测试做成极限压力,实际用户操作之间是有间隔的,用户不可能毫秒不停地在界面上点。性能测试阶段要设置合理的思考时间,模拟真实行为;压力测试阶段则不应该考虑思考时间,目的就是尽量压满。
第三,忽略JVM参数对测试结果的影响。同一套代码,JVM堆大小不同、GC算法不同,压测结果可能相差好几倍。所有对比压测必须在一致的JVM参数下进行。
第四,只看平均不看重负载拐点。压测报告不要只写“平均响应时间200ms”,要画出不同并发下的Tps和响应时间趋势图,标出拐点和崩溃点。这样上线前的容量评估才有意义。
第五,数据没有清理,越压越慢。如果压测过程中产生了大量脏数据,并且没有清理就进行下一轮压测,数据库表膨胀、索引维护、缓存失效都会让后面几轮的结果异常。每一轮压测结束,尽量重置数据库到初始状态,或者至少清理本轮的坏数据。
7. 最后分享一点个人经验
说了这么多工具、指标、步骤,每次给团队做压测培训和方案评审的时候,我最后都会放一句总结性的话:性能测试、负载测试、压力测试不是一次性的“过场”,而是一个持续循环的过程。项目上线前做一轮完整的体检,部署变更后做一轮回归验证,线上指标异常时再做一轮定向排查,这样才能让系统始终处于可控状态。
我个人在实际操作中的体会是,压力测试是最容易发现团队协作堵点的环节。它逼你梳理清楚每个接口的依赖关系、每个组件的配置细节、监控是否完善、响应预案是否落地。很多时候,压测结论早就不是技术问题,而是流程问题和管理问题。
最后再分享一个小技巧:压测报告的模板最好在项目早期就确定下来,统一格式。无论是性能测试还是压力测试,都写明测试环境、压测工具参数、监控数据、结论和调优建议这五块内容。这样后续做横向对比时,就不会因为每份报告格式不同而花费大量时间去对齐,归档和追溯都会方便很多。
如果你正打算开始给你手上的系统做第一次压测,那就从搭建一套JMeter脚本、设置好监控、先跑一个性能测试开始吧。等这轮流程走通了,再慢慢扩展做负载测试和压力测试,整套体系自然就建立起来了。