ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

性能测试全流程实战:从需求分析到瓶颈调优

性能测试全流程实战:从需求分析到瓶颈调优 1. 需求分析与性能测试的整体设计1.1 为什么性能测试必须先咬透需求很多人一上来就打开压测工具先把接口地址填进去再说。我见过太多这种场景最后测出来的结果除了给自己看根本没法说服任何人。性能测试的第一步从来不是搭脚本而是把需求文档翻烂把每个指标抠清楚。做性能测试的真正目的不是“测出一个TPS数字”而是回答三个问题系统现在能扛多少、瓶颈在哪里、距离目标还差多少。这三个问题都绕不开需求分析。拿最常见的电商下单流程举例。业务方说“系统至少要支持一万用户同时在线”这句话看起来很简单但如果不拆解你根本不知道该拿什么去压。一万用户同时在线不代表一万用户同时下单。真实场景可能只有百分之几的人在下单大部分人在浏览。真正需要关注的并发下单量可能只有几百远没有想象中那么大。需求分析阶段必须跟产品、开发、运维反复对齐的核心点包括业务量级日活、峰值在线、核心操作频次这些数字从哪来是否经过业务侧确认时间分布是否存在明显的波峰波谷比如电商大促、节假日抢购、月底结算操作比例读操作和写操作的比例浏览、搜索、提交订单分别占多少数据规模数据库里存量数据多少压测时用多少因为数据量级直接影响查询性能网络环境用户是走内网还是公网移动端占比多少有没有弱网场景要求这些信息如果需求文档里没有就主动去找产品和对口同事确认。问一次不清楚就问两次千万不要自己想当然。我踩过最大的坑之一就是按照自己的猜测去定指标结果压测报告出来以后业务方根本不认说这不是他们的真实场景最后整个测试周期被拉长了一倍。1.2 指标定级与目标值的确定逻辑需求对齐之后就要把指标目标值定下来。这一步做不好后面所有工作都白搭。性能指标一般分为三类对应的标准完全不同核心指标如下单成功率、支付成功率这类指标不达标就是事故必须严格卡住关键指标如响应时间、吞吐量直接影响用户体验需要按业务场景分级设定参考指标如服务器CPU使用率、数据库连接数主要用于定位瓶颈辅助判断目标值的确定不能拍脑袋。最稳妥的方式是看历史数据如果系统已经上线过一段时间线上监控里积累的指标数据就是最好的基准。比如线上TPS日常在500左右大促时冲到2000那么压测目标就可以定为正常情况1.5倍峰值情况按2到2.5倍去压。没有历史数据的新系统可以按业务预估量推算。这里给一个常用的简化计算方法假设预估日订单量是10万笔核心高峰期集中在2小时内则平均TPS 100000 / (2 × 3600) ≈ 13.9。再考虑峰值系数至少2倍目标TPS可以定为28到30左右。如果是秒杀或抢购类场景峰值系数要到5到10倍那就是70到140的TPS。这里要特别注意TPS目标不是一个人拍脑袋定的最终一定要以评审的形式跟各方确认。把计算过程、数据来源、假设条件都写清楚让大家在评审会上确认签字后面即便出了问题你也有依据可查。这是保护自己也是让测试工作更专业的方式。1.3 场景建模从业务链路到压测模型需求分析完成后最关键的一步就是把真实业务转化为可执行的压测场景。这一步直接决定了压测结果能不能代表生产环境表现。场景建模的核心有两个业务模型和请求模型。业务模型是什么样的就是搞清楚一段完整业务流程里每个操作步骤的占比和顺序。拿下单场景来说真实用户的行为路径是浏览商品、搜索、加入购物车、确认订单、提交订单、支付。每一步都有响应时间、成功率和并发量也都有各自的调用链和资源消耗。如果把压力全部打在提交订单一个接口上测出来的结果跟真实情况差之千里。在做业务建模时我常用的一种方法是把业务操作按“频率×资源消耗”划分成三类类型特点典型操作压测占比高频低耗调用频繁、单个消耗小浏览、搜索、查看详情60%-70%中频中耗频率适中、消耗中等添加购物车、确认订单20%-30%低频高耗频率低、资源消耗大提交订单、支付回调5%-10%请求模型则是把业务模型翻译成压测脚本。这里必须考虑到具体参数怎么构造。比如用户ID不能全用同一个否则数据库索引、缓存命中率都会被污染结果失真商品库存数据要顾及扣减逻辑和超卖问题订单号要有唯一性约束否则数据库主键冲突导致报错。一个容易忽略的痛点是压测过程中的参数化相关性处理。比如下单成功后会生成一个订单ID这个ID要传给支付接口使用。很多人写脚本的时候只盯着下单接口没有做实时的参数传递和关联提取导致压测到支付阶段时不断报错还以为是系统问题。这一类问题在场景建模时就要提前设计好关联规则而不是等脚本跑不通了再修。1.4 测试环境与数据准备决定结果可信度的地基场景模型定好之后接下来要做的是环境准备和数据准备。这个环节看着琐碎实际上是决定性能测试结果可信度的关键。首先明确一个原则性能测试环境尽可能贴近生产环境。但这种贴近不是绝对等价。生产环境的机器配置、网络拓扑、中间件版本、数据库数据量这些都应该尽量一致因为CPU核数不同、内存不同、带宽不同都会直接影响性能指标。能做成跟生产1:1镜像当然最好但在很多公司里这是不现实的这时候就必须做好“等比缩放”的规划并且把缩放关系在报告里写清楚。比如生产环境是4台应用服务器压测环境只能给2台那你测出来的结果要按这个差异进行说明不能直接拿压测环境的绝对TPS去对标生产目标。数据准备这一块是很多人翻车的地方。压测的数据量如果远小于生产环境数据库的SQL执行计划就会不一样。生产环境千万级数据的表查询走的是索引而你压测环境只有十万条数据优化器可能直接走全表扫描查出来的响应时间快得离谱。这个结果不但不具有参考性反而会误导判断。数据准备至少要做到几点核心表的数据量级与生产一致尤其是订单表、用户表、库存表这些热点表数据分布要均匀比如用户ID不要连续集中在某段区间避免数据库页缓存命中率虚高造数要覆盖边界情况比如库存为零的商品、超时未支付的订单、状态异常的记录压测产生的脏数据要清理否则多轮压测的数据会互相干扰有些公司为了省事直接用生产环境备份的数据脱敏后灌入压测环境这是很好的做法。如果做不到就需要自己写数据生成脚本或者用开源的数据构造工具但无论如何都不能用少量数据硬凑。我在早期做测试时就曾经因为数据量不足测出来的下单接口平均响应时间只有50毫秒结果上了生产之后实际响应时间要300毫秒被业务方直接质疑测试专业性。后来把数据量补齐之后才真正找到了问题所在。2. 压测工具选型与脚本开发的核心细节2.1 工具选型开源的够用商业的看需要压测工具的选择会直接影响整个测试效率和结果的可信度。工具不在多在于合适。我用过的工具不算少从早期的开源工具到商业方案都有接触各自适用场景差别很大。开源工具里JMeter是我最常用的选择。主要原因是它几乎覆盖了所有我能想到的协议场景HTTP、HTTPS、WebService、JDBC、JMS、TCP甚至Dubbo这类RPC协议也能通过自定义插件支持。社区生态很成熟遇到问题搜一下基本都有答案。对于绝大多数公司来说JMeter加上配套的监控插件已经能支撑80%以上的性能测试场景。Locust的高并发模拟能力和Python脚本编写方式在某些场景下非常顺手但它的断言和报告能力相对轻量。Gatling的Scala DSL写起来优雅性能表现和报告也很漂亮但学习门槛高一些。商业工具像LoadRunner和NeoLoad功能强大从网络协议层面能做到更精细的模拟但价格不低。我个人的建议是团队里至少有一位熟练使用JMeter的成员。因为开源性让你没有授权限制社区也足够强大遇到问题不必等待厂商支持。如果公司预算充足也可以通过商业工具获取更强的网络级模拟和专业支持。但无论选哪种都要基于实际场景模型来选择量级大、场景复杂的用功能强的简单校验型的用轻量的即可不要过度设计。2.2 脚本开发的规范与要点不是能跑就行脚本开发是很多人忽视但实际非常重要的环节。因为脚本质量直接影响测试结果的真实度而很多问题恰恰藏在脚本里。脚本开发必须遵循的几个原则我逐一展开。第一参数化一定要做彻底。常用的参数包括用户ID、商品ID、订单号、手机号这些参数如果写死压测根本测不出真实效果。拿用户ID来说如果几百个并发全部用同一个用户ID则数据库连接池的会话可能被复用缓存也会集中命中同一份数据导致响应时间虚低。参数化方式有很多CSV文件、数据库读取、函数生成均可但根据业务规则选择适当的方式不要一股脑全用随机函数。第二关联关系必须处理好。业务链路中存在大量上下游依赖前面接口返回的订单ID、Token、交易流水号要能自动化提取并传给后续接口。这个做不好压测跑到后面全是报错根本压不到真实业务逻辑。第三断言要合理。压测脚本中如果不加断言接口返回500也算成功那TPS再高也没意义。但断言也不能过度复杂否则会消耗额外资源。通常的做法是校验Http状态码为200同时校验核心业务字段比如返回码为0、或包含成功标识。既不过度也不缺失。第四脚本的“思考时间”要精心设计。真实用户从“浏览商品”到“点击下单”之间是有间隔的这个间隔就是思考时间。完全没有思考时间的压测是极限压力测试不是业务真实性测试两者目标完全不同。在做容量评估和容量规划时往往需要的是结合思考时间的混合场景模型极限压测一般用于评估系统底线。我自己在脚本开发中都会做一层规范化封装。通用函数提取到公共模块参数文件统一管理脚本结构按业务模块分层。前期可能觉得这是多余功夫但一旦压测场景多起来规范的脚本维护成本远低于一锅粥式的脚本。2.3 执行策略压测节奏不是一把梭脚本准备好之后真正进入到压测执行阶段节奏控制尤为关键。压测执行要分阶段走不要上来就全量并发猛打。道理很简单就像人跑马拉松要先热身再加速直接冲刺会导致数据失真也容易给系统造成短暂冲击把故障误判为容量不足。我常用的执行策略是分层加压单接口基准测试先对单个接口单线程持续压测摸清基础响应时间和成功率这一步通常跑5分钟单接口梯度加压从50并发开始逐步加100、200、400、800每个梯度保持一段时间观察各项指标变化趋势混合场景压测按业务模型比例同时压测多个接口模拟真实用户行为链路稳定性测试在70%目标负载下持续运行至少30分钟观察是否存在内存泄漏、连接池耗尽等问题峰值与突发测试模拟瞬时流量冲击比如秒杀场景下的5分钟集中请求每一阶段之间要保留观察窗口让系统恢复稳定状态再进入下一轮。切忌一个场景跑完马上跑下一个中间不清理数据、不检查系统状态那样得到的指标是混在一起的很难定位问题。执行过程中监控是同时进行的不只是看压测端的TPS和响应时间还要同步看服务端指标CPU、内存、磁盘I/O、网络带宽、GC频率、数据库连接池状态、慢查询数。压测端数据和服务端数据只有放在一起看才能形成完整证据链。举个例子如果TPS上不去但压测端响应时间不断增加同时数据库慢查询也在增长那问题大概率在SQL或者索引层面而不在应用服务器。2.4 全链路压测与容量规划的一些经验近些年的趋势是做全链路压测也就是从网关到应用、到中间件、再到数据库整个链路全部纳入压测范围。这种方式的优势是能发现在模块层面联调时才暴露的问题比如某些环节的超时配置不一致、连接池大小不匹配。但全链路压测对环境和数据隔离的要求非常高。压测流量如果不加区分地打入生产环境会造成脏数据落地。常见的做法是在请求入口打上压测标识业务代码在一些关键环节识别到标识后会走独立的影子库或影子表压测数据不污染真实业务数据。在容量规划方面我对团队的建议是至少保存三轮有效压测数据再做趋势评估。第一轮是基线第二轮是优化后对比第三轮是小幅加压的边际效应分析。不同轮次的测试结果要放到同一个维度下比较比如都统一在相同并发数、相同数据量下做否则数据之间没有可比性容量规划也就无从谈起。3. 性能问题定位与调优的完整路径3.1 瓶颈分析的正确打开方式压测做完之后真正的重头戏才刚开始。压测结果不理想是常态可关键是你得能从一堆数据指标里揪出真正的瓶颈所在。这环节极其考验打印经验也是最容易走弯路的地方。我的习惯方法是自上而下逐层排查。先看应用层再往下看中间件最后看数据库和操作系统。为什么这样排因为性能问题的表象通常出现在上层比如用户侧响应慢但根因往往在下层比如数据库索引失效或者连接池阻塞。如果一上来就去翻数据库慢查询而忽略了应用线程阻塞的问题就会白白浪费大量时间。应用层的排查重点是线程状态和GC情况。用线程Dump看是否有大量线程处于BLOCKED或WAITING状态用GC日志看GC频率和停顿时间。如果一次性Full GC的停顿时间超过几百毫秒接口响应自然会变慢TPS也会受到明显影响。中间件层主要看的是连接池、缓存和队列。数据库连接池是否被打满Redis响应时间是否异常消息队列的积压量有没有持续上升这些往往是整套系统中的水桶短板。数据库层的排查是最常见的瓶颈所在。慢查询日志是首要关注点。看到一条查询耗时两秒以上就要去看执行计划确认是否走索引、有没有全表扫描、预估扫描行数是多少。操作系统层面主要关注的是资源水位。比如通过监控看到CPU使用率已达90%以上就可以判断CPU是瓶颈如果内存大量交换到Swap就要考虑内存容量问题磁盘I/O等待时间过长则要重点关注磁盘性能尤其是数据库所在盘。这种逐层排查的方式能让你快速圈定大方向然后逐步聚焦到具体问题的代码层面。不要试图一次性分析完所有数据那是浪费时间抓住主要矛盾先解决最严重的那个问题往往整个系统的性能就能获得显著提升。3.2 常见的性能瓶颈类型与典型调优手段性能问题虽然表象多样但归结起来不外乎几种类型。把每种类型对应的典型场景和调优手段搞清楚排查效率会高很多。CPU密集型瓶颈的一个典型特征是CPU使用率持续高位但TPS上不去响应时间却在增长。这种时候需要重点排查是否存在死循环、密集计算、或者是序列化/反序列化开销过大等问题。比如某些框架的内存序列化操作在低并发下看起来没事一上高并发就暴露出严重的CPU消耗。磁盘I/O瓶颈也是高危类型。常见于大量日志写入、数据库频繁刷盘、或者文件读写密集的业务场景。如果是数据库所在磁盘导致I/O等待过高可以考虑让数据库表和索引分离存储在不同磁盘或者调整日志的写入策略。内存与GC问题在高并发Java应用中非常普遍。堆内存设置不合理对象创建过于频繁或者存在内存泄漏都会导致GC频繁。解决方向包括调整堆大小参数、优化对象创建逻辑、使用池化技术复用对象。GC调优要基于GC日志分析不要盲目修改参数。数据库层面慢查询和索引失效是两大高频问题。慢查询的典型处理方式是分析SQL执行计划对高频查询条件建立合适的联合索引避免在WHERE条件字段上使用函数导致索引失效。数据库连接数配置过小在高并发下会导致大量线程等待获取连接表现为接口RT急剧上升。这类问题的判断方法是看连接池是否被打满以及等待获取连接的时间是否异常。锁竞争问题在高并发写场景下很容易暴露。比如数据库行锁竞争、分布式锁的频繁等待、以及应用层的同步锁。这类问题的处理往往需要结合业务逻辑来优化甚至可能需要引入异步化或者队列不是单纯改参数就能解决的。我整理的常见瓶颈快速判断参考如下表症状可能瓶颈首选排查方向典型手段响应时间涨、TPS不涨应用层线程阻塞/锁线程Dump、锁等待分析消除锁竞争、线程池调优CPU居高不下业务计算密集/GC频繁CPU火焰图、GC日志代码优化、调整JVM参数I/O等待高磁盘能力不足/刷盘频繁iostat、数据库配置磁盘扩容、日志/tmp分离、调优刷盘策略连接数打满连接池配置过小/泄漏连接池监控、活跃连接数池大小调优、排查连接泄漏数据库响应慢SQL执行计划问题慢查询日志、执行计划优化索引、改写SQL内存持续上涨内存泄漏/缓存过大堆内存监控、Dump分析修复泄漏、控制缓存上限3.3 调优验证的方法改一处测一次调优过程中最有价值的习惯是“一次只改一个变量改完就测”。这个道理很简单但实际操作时很多人忍不住一次性调整多个参数结果出了问题根本看不出来是哪一项改动带来的效果。举例来说你发现数据库连接池太小影响了TPS同时又觉得JVM堆参数不合理。如果同一次跑压测前把这两个都改了测试通过还好一旦性能没有提升甚至下降你就无法判断是哪一个改动出了问题只能回头逐个还原白白浪费时间。正确的做法是先只调连接池大小重新压测对比结果确认有效之后再去调整JVM参数再压测。每一步都留下完整记录包括改动内容、改动前后的测试结果。这样你每做的一次调优到底有没有用一清二楚。调优后的验证不是只看一个指标至少要从三个维度对比TPS是否提升、平均响应时间和P95/P99是否下降、服务器的资源消耗是否更合理。有时候TPS上去了但P99反而变差说明长尾请求变多了这对用户体验来说并不是真正的优化。举个例子某接口平均响应时间从300毫秒降到150毫秒但如果P99从800毫秒涨到1200毫秒那说明少数用户的体验变得更差了这在性能优化中是不可接受的。另外如果有条件一定要做多次重复测试取均值尤其是一些对系统影响较大的调优场景。单次测试结果受网络波动、系统自身不稳定因素影响较大。至少跑三轮取相对稳定的一组作为结果才更可靠。4. 压测报告编写与问题排查工具集4.1 一份有说服力的报告应该包含什么性能测试报告是整个性能测试项目的最终交付物也是对外沟通的桥梁。报告写得专业与否决定了你的工作成果能不能被业务方和技术团队充分认可。我见过很多测试工程师把大量精力花在执行上却对报告敷衍了事最后效果大打折扣。写报告时一个核心认知是你的读者不全是技术人员。业务方关心的是能不能支撑业务目标管理层关心的是要不要加机器、需要花多少钱只有开发团队才会关心具体的瓶颈点和方法论。所以一份合格的报告必须兼顾两类读者的诉求既要有技术细节的深度也要有结论的清晰表达。我的报告结构一般是这样组织的测试概述项目背景、测试目标、测试范围和时间窗口需求与指标定义本次压测的核心指标和通过标准比如目标TPS、目标RT、成功率测试环境说明软硬件配置、网络拓扑、数据规模、是否与生产一致测试场景设计业务模型、并发模型、压测规模和持续时间核心结果数据TPS、RT平均/P95/P99、成功率、错误率等关键指标务必用表格呈现资源监控数据应用服务器、数据库、中间件的关键监控指标问题清单与调优进展发现的问题、严重级别、当前状态、调优前后对比结论与建议是否达到目标、容量预估、扩容建议、后续优化的优先级4.2 数据呈现的细节技巧写报告的时候数据呈现方式直接决定了读者能不能快速抓住重点。对于技术人员你需要的是趋势图、分布数据和原始明细对于业务方和管理层你需要的是一目了然的结论性数据。表格是报告里最常用的呈现方式。TPS、响应时间、成功率、错误率这些关键指标一定要放表格里不同场景分不同行使对比清晰。比如基准场景、梯度场景、混合场景、稳定性场景分别列出各自的TPS和RT读者一眼就知道系统在不同负载下的表现。趋势图的作用是展示变化过程反映性能拐点。一个典型的场景是当并发从200升到400时TPS从300升到500这是一个健康的上扬但当并发从400升到800时TPS不升反降同时RT暴涨这就说明系统已经达到瓶颈。这种拐点用图呈现的效果最直观。P95和P99这两个分位值非常值得重视。平均响应时间容易被极端值掩盖比如一条超时20秒的请求把几十条50毫秒的请求平均值一下子拉高了。而P95和P99反映的是绝大多数用户的真实体验尤其是P99它代表的是最差的那1%用户所感知到的性能。给接口调优时我通常更关注P99的变化。真正有说服力的报告是平均RT不高但P99却异常高这种对比能直观地表明确实存在长尾问题具备更大的分析价值。资源使用情况也是报告的关键组成部分。应用服务器的CPU峰值、内存水位数据库的慢查询数量和连接池使用率这些数据与TPS、RT结合在一起才能解释“为什么系统表现是这个水平”。比如TPS上不去但数据库慢查询数量一直在涨两者放在同一张报告里结论自然浮出水面。4.3 常见问题与排查技巧实录压测执行过程中必然会遇到各种问题有些是系统自带的有些是脚本自身带来的。把所有问题都留给开发去解决是不对的很多问题明明就是压测方案设计不到位造成的。脚本相关的高频问题最典型的是压测结果被参数相关性困扰。比如前面接口提取的Token过了有效期后续接口不断报权限异常。这类问题的排查方向是看错误日志中报错的具体原因而不是一有问题就觉得系统挂了。确认问题来源之后再去调整脚本的关联逻辑。我自己的习惯是压测过程中随手开着后端日志窗口一旦看到报错就立刻对照请求体和返回信息判断到底是脚本问题还是系统问题。另一种高频问题是压测机被打满。很多人忽略了压测机自身的性能限制。如果JMeter部署在一台低配机器上一旦并发数上去压测机自己的CPU先打满了这时无论系统性能多好测试结果都不可能准确。判断的方法很简单监控压测机的CPU和内存如果压测机CPU持续100%就需要横向扩容压测机了通常用一台额外机器做分布式施压。系统类问题中最常见也最容易被忽视的是压测前的缓存预热。生产环境中有大量热点数据常驻缓存而测试环境是冷启动状态Redis里什么都没有所有请求都直接打到数据库导致首次压测的数据库压力巨大甚至出现大量连接超时。这种问题不解决测出来的结果毫无参考价值。我的做法是在压测前做一次“预跑”先用低并发跑上一段时间让缓存和数据页填满等系统稳定下来再进入正式压测阶段。另一个实用的排查小技巧是当TPS出现断崖式下跌时立刻看两个地方数据库连接池和中间件线程池。这两个池子一旦被打满系统会表现为“突然降速但并没有崩溃”。原因通常是池的排队等待时间暴增超时丢弃大量请求。遇到这种情况可先做一个快速的定位测试降低并发数到原来的1/4如果系统迅速恢复正常基本可以确认是某个池子容量不够了。4.4 一个值得一用的小工具集工欲善其事必先利其器。性能测试期间用对工具能节省大把时间。这里分享一些我每次压测都会用到的工具组合全部基于开源方案没有授权问题。压测执行JMeter作为主压测工具配好参数化与断言监控采集Node Exporter配合时序数据库存储用可视化面板展示服务端指标一台机器即可搭建起来JVM监控JDK自带的Java Flight Recorder排查GC和线程问题相当趁手数据库排查MySQL慢查询日志配合自带的性能分析工具加上EXPLAIN查看执行计划日志分析先用手头常用的日志检索工具或者一条awk命令提取关键信息快速定位异常集中点这套组合完全免费但覆盖了从施压到监控、再到定位问题的完整链路。团队小、预算不多的情况下这套方案足够支撑绝大部分性能测试需求。用这些工具时有个经验想分享。监控面板不要只看眼前那几分钟的数据。压测过程中要定时回看整个时间窗口的趋势图因为很多性能问题有一个缓慢演化的过程比如内存会随着测试时间推移不断增长。只盯着实时数值看很难发现这种渐进的趋势而趋势恰恰是定位内存泄漏类问题的最重要线索。5. 收尾前的一些真心话性能测试做到最后真正拉开差距的地方其实已经不是工具用得熟不熟、脚本写得好不好而是对整个测试过程闭环的把控能力。从需求到执行从定位到报告每一环都要经得起推敲。我个人的体会是性能测试报告的结尾不应该只是罗列问题更要有明确的问题优先级和整改建议。建议要能落地比如“连接池参数建议从20调整到50预期可提升多少”这样的表述而不是笼统说“建议优化系统性能”。报告只有做到这个颗粒度才会被开发和运维真正重视测试工作也才能真正推动系统进步。最后再分享一个小技巧。无论项目多急压测执行完了一定要留出时间回看一遍全部脚本和配置特别是参数化文件里的数据是否被耗尽、关联提取的正则表达式是否涵盖边界情况。很多压测数据的异常最后追根溯源都是这些“小细节”的问题。性能测试没有惊心动魄的高光时刻靠的恰恰是细节处一点一滴的累积。把这些基础功夫做扎实了你写的每一份报告都会站得住脚。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表