压测实战:数据构造与JMeter参数化方案详解

发布时间:2026/10/18 19:31:00
压测实战:数据构造与JMeter参数化方案详解
开头上次接手轻商城项目的压测任务我以为自己准备得很充分了。接口文档看了一遍场景也梳理清楚了然而真正开始压测的时候TPS死活上不去应用日志里全是锁等待和超时异常。排查了一下午才发现本质问题不在系统而在于我自己的压测脚本——所有虚拟用户都在抢同一个测试账号的购物车数据。这就是压测数据没做参数化的典型现场。这才意识到压测工程的老司机和新手之间的差距往往不在工具用得多溜而是在准备工作——测试数据构造、脚本参数化设计这些不显山露水但是决定结果可信度的环节。这篇文章就把我做完整个轻商城项目压测的完整流程记录下来从Python构造10万条测试数据开始到JMeter脚本的参数化设计收尾中间踩过的坑、试出来的经验都毫无保留写出来。适合正在做Web电商类项目压测、或者准备系统学习性能测试的朋友照着这个流程走一遍你的压测会比之前拍脑袋式的做法靠谱得多。1. 压测前的数据工程准备1.1 压测为什么绕不开造数据这道坎很多人做压测的时候有个误区拿线上环境直接压。线上环境有真实数据和真实流量听起来很美好但出了问题影响面太大基本不允许这么干。所以都会退而求其次搞一套和生产架构一致的测试环境然后往里塞数据。但测试环境的数据量往往少得可怜。几个测试账号、几十条商品记录这种数据量做功能测试没问题拿来做压测就完全失真了。为什么我举几个很具体的场景。轻商城项目里的商品搜索分页接口数据量只有100条的时候不管查第1页还是第10页走不走索引性能都差不多每秒处理上万个请求轻轻松松。但如果真有10万条商品SKU分页查询从第1页翻到第1000页SQL执行计划的走向完全不一样。翻页越深偏移量越大慢查询就出来了。不提前把这层数据垫起来你压出来的分页接口性能数字就是空气。再比如商品详情页。压测的时候如果所有并发用户都命中那三五个商品IDRedis缓存会命中得极其好看击穿、穿透、雪崩这些真实场景完全看不到。但真实系统里的数据分布是海量的、分散的缓存命中率和热点分布都是另一回事。还有更隐蔽的问题——订单提交流程。商品库存只有几十件的时候高并发下单瞬间就把库存扣光了库存扣减的锁竞争根本压不出来。真实的库存数据是分SKU、分仓位的热销品的库存和滞销品的库存压测效果截然不同。所以压测之前先把数据基础打好这步偷懒了后面所有结果都会被质疑。轻商城这种项目的数据量级我们按10万来准备这个量级既能反映出索引、缓存、锁竞争的真实压力又不至于因为导入数据本身就耗时太长影响整个压测节奏。1.2 Python造数脚本的设计思路确定了要造10万条数据接着就是怎么造的问题。有人可能会说数据库里写个存储过程循环插入不就行了或者直接用存储过程跑几十分钟反正不影响压测。话是这么说但生啝测试数据这件事本身也是有讲究的我倾向于用Python来写主要就三条理由第一数据生成规则灵活。电商场景里的数据都有内在的业务逻辑用户注册时间和下单时间得匹配、用户等级和消费金额得对得上、商品SKU颜色和尺码的组合要有规律。Python脚本里写这些逻辑非常自然也方便后续调整规则。第二可以真正模拟业务形态。用Faker库生成姓名、手机号、地址生成出来的数据在字符串分布上更接近真实用户的行为。比如用户名的长度分布、手机号的前缀规律这些细节在功能测试时无所谓但在压测场景下会影响数据库索引的区分度和字符串比较的性能。第三插入过程可控。Python连数据库批量插入能精确控制每次插入的数据量还能记录插入进度。出了问题从断点继续跑就行不用从头再来一遍。来直接看这个造数脚本的核心逻辑我用的是MySQL数据库主表就是用户表、商品SKU表和购物车表。简单说下这三张表的结构关系user表是主表cart表有user_id外键关联user表sku表是类目分析维度的主数据表。造数顺序必须是先user和sku再cart因为cart引用了前面两张表的主键。import faker import pymysql import random from datetime import datetime, timedelta fake faker.Faker(zh_CN) conn pymysql.connect( host10.0.12.36, port3306, userload_test, passwordyour_password, databasemall, charsetutf8mb4, autocommitFalse, cursorclasspymysql.cursors.DictCursor ) # 一次插入500条是性能和事务大小的平衡点 BATCH_SIZE 500 TOTAL_USERS 100000 user_sql INSERT INTO t_user (username, nickname, phone, email, gender, user_level, reg_time, status) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) start datetime.now() cursor conn.cursor() for i in range(0, TOTAL_USERS // BATCH_SIZE): batch_data [] for _ in range(BATCH_SIZE): reg_time fake.date_time_between(start_date-3y, end_datenow) batch_data.append(( fake.user_name() str(random.randint(1000, 9999)), fake.name(), fake.phone_number(), fake.email(), random.choice([0, 1, 2]), random.choices([1, 2, 3, 4, 5], weights[40, 30, 20, 8, 2])[0], reg_time, random.choices([0, 1], weights[98, 2])[0] # 2%的注销用户 )) cursor.executemany(user_sql, batch_data) conn.commit() if i % 20 0: print(f已生成 {(i1) * BATCH_SIZE} 条用户数据耗时 {(datetime.now()-start).seconds}s) cursor.close() conn.close() print(f用户表造数完成总计 {TOTAL_USERS} 条耗时 {(datetime.now()-start).seconds}s)脚本里有两个细节我特别说明一下。第一就是批量大小我试过一次插100条、500条、1000条500这条线上整体表现最稳。太少的话网络往返次数太多太多的话事务负担大、内存占用高万一中间失败回滚也麻烦。第二就是用户名拼接了随机数这个不影响业务主要是防止Faker生成重名导致唯一索引冲突。真实环境里可以通过打标签或者分布式ID方案解决但压测环境用上面这种方案最省事。1.3 关联表数据和索引的配合user表10万条数据造完只是第一步但电商项目的接口不只是登录和用户列表购物车、订单、商品搜索都依赖各自的表。所以关联表的造数要分两步走。第一步是确认关联表的外键能不能对上。最省事的做法是把user表的主键ID批量读出来存到列表里然后生成购物车数据的时候从这个列表里随机取。这样就不会出现购物车的user_id在user表里找不到的情况省去后续清理脏数据的麻烦。# 先把用户ID全部取出来 cursor.execute(SELECT id FROM t_user) user_ids [row[id] for row in cursor.fetchall()] # 生成购物车数据每个用户1~5条商品记录 cart_sql INSERT INTO t_cart (user_id, sku_id, quantity, checked, create_time, update_time) VALUES (%s, %s, %s, %s, %s, %s) sku_pool list(range(1, 10001)) # 假设sku表有1万个有效商品 cart_batch [] for uid in user_ids: for _ in range(random.randint(1, 5)): cart_batch.append(( uid, random.choice(sku_pool), random.randint(1, 3), random.choice([0, 1]), datetime.now(), datetime.now() )) if len(cart_batch) 500: cursor.executemany(cart_sql, cart_batch) conn.commit() cart_batch.clear()第二步就是索引。很多人在造数的时候不关心索引但压测能不能压出真实效果一半功劳在这里。t_user表的reg_time字段如果查询类压测场景涉及用户注册时间筛选这列没索引就等着全表扫描吧。10万条数据的全表扫描在压测机上MySQL还是扛得住的但并发拉高之后肯定出事。t_cart表的user_id外键索引、t_sku表的category_id索引这些是在设计表结构的时候就该建好的。如果表结构已经上线了才想起来那就用ALTER TABLE补上建索引本身也会消耗一点时间但是值这个钱。我后面在排查慢查询的时候看到过一个经典场景某个压测接口响应时间从50ms慢慢爬到1300ms看慢日志发现SQL是在t_order表上做全表扫描过滤条件字段虽然建了索引但SQL写法里面用了函数包裹索引列导致索引失效。这类问题非常典型不靠真实数据量压测根本发现不了。2. JMeter参数化方案的设计与选择2.1 为什么必须参数化而不直接写死数据造好了接下来到JMeter脚本环节。假设你有10万个用户账号、10万个不同的商品ID如果你的HTTP请求里写死了一个手机号和商品ID那这些数据就全浪费了。更严重的后果是所有并发线程都在打同一个账号的登录接口、抢同一件商品的库存背后的数据库锁竞争、Redis热点、本地缓存穿透全都被放大了压测结果严重失真甚至会造成假瓶颈。参数化通俗理解就是把写死的请求数据替换成可以动态变化的变量让每一轮请求都使用不同的、真实存在的测试数据。这样10万条用户数据的价值就真正被利用起来了。轻商城项目的场景我用到了三种参数化方式每种方式适应的场景不一样。2.2 CSV数据文件参数化详解最常用也最推荐新手用的就是CSV Data Set ConfigCSV数据文件配置。把所有要用的测试数据都放到一个CSV文件里JMeter在发起请求前会从这个文件里读一行把对应列的值赋给变量。我看过不少人在这一步栽跟头。整理了三条关键经验。第一CSV文件一定要处理干净。如果用Excel导出CSV默认编码是GBKJMeter默认用UTF-8读取中文直接乱码。正确做法是从数据库导出CSV时用utf8mb4编码或者在文本编辑器里把CSV另存为UTF-8格式并且不要带BOM头。BOM头会导致第一列第一个变量多一个不可见字符请求发出去服务器解析参数名出错报错形式还比较隐蔽。第二注意CSV文件路径。JMeter脚本里写相对路径然后命令行启动时用 -f 参数指定测试计划所在的目录。永远不要用绝对路径。因为压测脚本可能要迁移到不同的压测机上执行绝对路径换台机器就失效了。第三也是很多新手经常忽略的CSV的变量名数量要和表头列数对应。JMeter读CSV时是按列顺序赋值的而不是按列名映射。表头有三个字段配置里只写两个变量名第三列就被静默丢弃。你感觉参数化成功了实际上用例用的还是同一个值。这里给一个轻商城项目登录接口的CSV配置示例。CSV文件第一行是表头后续每行是真实数据username,password,phone,device_id test_user_0001,123456,13800138001,abc123 test_user_0002,123456,13800138002,def456 test_user_0003,123456,13800138003,ghi789在JMeter里添加CSV Data Set Config文件名填user_data.csv变量名称填username,password,phone,device_id分隔符用英文逗号Allow quoted data选择FalseRecycle on EOF选择TrueStop thread on EOF选择False。Recycle on EOF这个参数很关键。如果把Stop thread on EOF设为TrueCSV文件读到底之后线程直接结束压测时长就不是你设定的值了。设成循环使用才能保证在固定压测时长内并发线程一直正常运行。2.3 出现的另一个重要场景从数据库直连读取参数CSV文件方式简单易用但有一个问题如果压测数据本身是动态变化的那CSV文件就搞不定了。举个例子轻商城的订单提交流程需要先创建订单返回一个订单号然后用这个订单号去请求支付接口。这种先A后B的关联场景用CSV参数化是没法实现的。因为我根本不知道每次创建订单会生成什么ID这是系统运行时的数据不是预先准备的数据。这种场景就要用到正则表达式提取器或者JSON提取器把第一步响应里返回的订单号提取出来存到变量里然后在第二步请求里引用这个变量。还有一种场景是用JMeter的JDBC Request直接查数据库把查询结果作为参数传给后续请求。比如压测一个用户维度的接口同一个用户反复请求没有任何压力我要确保每次请求都是不同的用户。那就用JDBC Request查询一张独立的user_id表然后通过ForEach Controller或者直接配置来循环使用。我一般建议能用CSV就用CSV可读性好、调试方便。JDBC方式留给必须实时生成数据的场景。你如果一上来就全链路都用JDBC压测机本身就会成为瓶颈因为每个线程都在反复建数据库连接这会让压测的并发数变成了假的并发。JMeter的JDBC配置有几个参数容易出错放在这里说明。JDBC Connection Configuration里的Database URLMySQL的写法是jdbc:mysql://host:port/db?useUnicodetruecharacterEncodingutf8会自动带上重连和编码参数。JDBC Driver Class选com.mysql.jdbc.Driver。连接池的Max Number of Connections要和线程组最大并发数匹配否则线程多了之后全部等待获取连接压测结果就是一堆超时。2.4 函数助手的灵活应用除了CSV和JDBCJMeter还有一批内置函数可以用来生成参数。最常用的是 __Random比如生成随机手机号尾号、随机金额或者随机数量。${__Random(1,100000,user_id)}这个函数会生成一个1到100000之间的随机数把结果存到user_id变量里。用在哪呢。比如某个测库存扣减的接口我不想让它总是命中同一个SKU那在SKU ID参数位置直接写上面的函数就能实现。但要注意__Random生成的是范围随机数不代表数据真实存在。如果表里的SKU ID不是从1连续编到100000的随机数命中的很可能是空数据或者不存在的ID。这种情况接口返回的正确性就受影响压测本身就是测正常业务路径不能频繁测异常路径。所以更稳的做法是先用SQL查出一批真实存在的ID做成CSV文件再用CSV参数化。函数助手只用在金额、数量、时间戳这种在合理范围内取值就可以的字段。3. 压测场景与线程组设计3.1 从单接口压测到混合链路压测数据准备到位、脚本能跑通接下来要聊的是怎么设计压测场景。我见过很多同学一上来就直接把整套业务流程所有接口串在一起压结果系统秒挂也不知道是哪个环节撑不住。正确的思路是从单接口压测起步把你系统的能力摸清楚再拼装成混合链路。轻商城核心接口大概分成这几类用户登录、商品列表查询、商品详情、购物车增删改、订单提交、支付回调。不同类型的接口压力特性完全不一样。登录和商品列表是读多写少购物车和订单提交涉及写库和锁竞争支付回调涉及外部系统交互。所以我设计压测的第一阶段是把核心单接口各压一轮。一轮一个线程组并发数从50开始往上爬。每个接口压完之后都记录一个核心性能基线的表看哪个接口离期望值最远。等所有单接口都压出一轮数据之后再按照业务占比去混合编排。比如正常电商场景里商品浏览的请求量是购物车操作的十倍左右下单的又是购物车操作的十分之一左右。按这个比例配置脚本里的线程数量或者吞吐量定时器才能模拟出真实的业务流量特征。3.2 线程组和参数匹配的细节线程组的设计直接影响压测数据的有效性和真实性。这里有一个非常核心的匹配问题并发线程数和CSV文件数据行数的关系。假设你有100个并发用户持续压测CSV文件里只有50行数据。如果CSV的Recycle on EOF是False50行读完之后后面的50个线程就完全没有参数可用直接报错。如果你的脚本是按单轮请求设计的而你的压测目标是持续压10分钟那CSV的数据量就必须足够大或者允许复用。我把这个关系梳理成一张速查表场景CSV文件设置线程组设置单轮压测数据多于并发数Stop thread on EOFFalseRecycle on EOFFalse循环次数1持续压测数据量有限Stop thread on EOFFalseRecycle on EOFTrue按持续时间压测数据必须唯一使用Stop thread on EOFTrue循环次数设为1每个线程用不同数据Sharing modeCurrent thread线程组内每个线程独立取行另外再提醒一个参数Sharing mode共享模式。默认的All threads共享同一个CSV文件的光标位置也就是不同线程轮流取下一行数据。这会导致同一个用户的多个请求在线程间被切碎不一定能模拟出用户会话连贯性。如果压测场景要求每个虚拟用户使用自己的专属数据那就要把Sharing mode改成Current thread当前线程这样每个线程从CSV第一行开始独立读取互不干扰。3.3 断言和监听器的正确配置压测脚本里断言的作用不是拿来判断功能对不对而是实时发现响应异常。常见的断言选择有两种第一种是响应代码断言检查HTTP响应码是否为200第二种是响应文本断言检查返回报文体是否包含指定JSON字段。做接口压测的时候我习惯加一个响应代码断言和JSON断言双保险既要看HTTP层是否正常又要看业务码是否正常。轻商城项目里的登录接口如果密码错误HTTP状态码依然返回200但响应体里面的code字段是50001这种就属于业务异常HTTP断言完全捕捉不到。监听器这里有个常见的性能陷阱。实际压测的时候不要挂View Results Tree查看结果树这个监听器它会记录每一个请求的完整报文压力稍微一大JMeter本身就会成为瓶颈CPU和内存全部被吃掉压测结果完全不可信。真正压测的时候只挂聚合报告Aggregate Report和Backend Listener把结果实时写入时序数据库或者日志文件里。聚合报告给出了关键的吞吐量、错误率、响应时间分布压测结束之后分析就基本够用了。4. 执行压测与结果分析实录4.1 压测执行前的最后检查清单脚本配完不代表马上就能开压。我在压真实业务之前有一套固定的检查清单按顺序过一遍能省掉执行过程中90%的返工。第一脚本里不能有调试用的请求。有时候调试的时候加了一个查看结果的HTTP采样器压测的时候忘了删实际压出来的聚合报告里就混入了它请求的数据整个压测结果就废了。第二检查参数化文件的行数和编码。命令行跑压测之前先把CSV文件打开看一眼确认没有空行、没有BOM、没有多余逗号。我在这里吃过很大的亏文件里多了一个逗号参数化后某个变量多了一个字符服务端解析失败率一路飙到30%。第三确认压测机和被测系统之间的网络延时。可以在JMeter脚本加一个空的HTTP请求压测机上直接ping一下应用服务器的IP看延时是否在合理范围内。如果延时超过5ms就要考虑是不是跨机房了压测结果里加进去的基线延时会让响应时间虚高。第四被测系统要有监控。压测开始之后你不仅要看JMeter的聚合报告还要看应用服务器的CPU、内存、连接数、数据库慢查询。没有这些数据你只知道系统挂了但完全不知道挂在哪里。4.2 一次真实压测的完整演进过程我把轻商城项目的压测过程完整记录一遍方便你对照自己项目康康。第一轮登录接口单压并发50。聚合报告显示TPS大约是1200平均响应时间42ms错误率0%。MySQL CPU使用率35%应用服务器CPU 22%。这个数据说明登录接口的压力还远没到瓶颈先把并发提到200。第二轮并发200。TPS涨到3800平均响应时间85ms错误率反而降到0.01%。应用服务器CPU到了55%MySQL CPU到了60%。这里误差率开始出现了查看日志发现是Redis连接池被占满部分请求获取连接超时了。这个发现很有价值说明Redis连接池配置太小成为登录链路的第一瓶颈。第三轮并发400。TPS没有像预期那样继续翻倍而是只到4500就上不去了平均响应时间飙升到300ms错误率到5%。MySQL CPU到了85%出现大量慢查询。查看慢查询日志发现问题集中在用户表的全表扫描定位到是SQL查询里用了WHERE phone LIKE 一个条件导致索引列失效了。这就是通过真实压测发现业务SQL隐患的典型案例。如果没有压到并发400这个慢查询根本不会暴露等上线之后真实用户一多直接被打爆。混合链路压测是按比例分配的。总并发400的前提下商品浏览占了250个线程登录50个加购物车60个提交订单40个。这种混合场景下的性能表现和单接口压测的数据完全不一样提交订单这个链路因为要操作库存锁响应时间波动非常大从平均80ms直接飙到500ms而且失败率高居不下。分析下来是数据库行锁竞争激烈这也是电商压测的经典症结。4.3 聚合报告数据应该怎么看很多新人拿到JMeter聚合报告就像拿到天书其实就那么几个关键列。Samples采样请求的总数。压测时长乘以TPS大致能对上对不上说明压测中断过。Average平均响应时间。这个数字会被极端值拉高单独看意义不大要结合百分位看。90% Line / 95% Line90%、95%的请求都在这个响应时间以内。这是衡量用户体感最核心的指标。有极端长尾的时候95% Line远比平均线更能说明问题。Error %错误率。电商系统压测目标通常要求小于0.1%。Throughput吞吐量也就是每秒处理的请求数。这是系统处理能力的核心指标。Received / Sent接收和发送的字节数。可以用来核验压测中网络是不是瓶颈。我习惯把聚合报告导成CSV在Excel里再用透视表看趋势。光看一个总平均值太粗糙。把TPS按秒拉出来能直观看到压测过程中有没有出现明显的性能衰减。如果前5分钟TPS稳定在4500第6分钟开始掉到3000后面持续往下走那大概率是哪一层资源被慢慢耗尽了比如连接池泄漏、内存持续增长触发GC。5. 压测过程中最常踩的坑与排查方法5.1 高频问题速查表问题现象根因排查思路解决方式CSV中文参数变成乱码文件编码不是UTF-8或带BOM用编辑器查看文件编码另存为UTF-8无BOM格式参数化后大量请求404或返回错误业务码参数引用的数据不存在或越界检查接口日志看具体参数值核对参数值和数据库是否匹配压测机本身CPU跑满TPS上不去监听器太多或者聚合报告开启查看压测机监控压测期间关闭结果树只保留聚合报告并发升高后大量连接超时数据库或Redis连接池太小看中间件日志和连接池监控调大连接池上限同样的请求响应时间越来越慢数据量增长导致索引失效或SQL恶化看慢查询日志优化SQL或补索引多个线程共享同一个用户导致数据错乱Sharing mode配置错误检查CSV配置的共享模式切换为Current thread模式压测结果和线上差异巨大测试数据量级和分布与线上不一致对比数据规模和分布重新构造更贴近真实的数据5.2 排查问题时的思路顺序我排查压测问题的基本顺序是先看数据是否正确再看服务器的日志然后看中间件的状态最后回到脚本本身。数据问题是最容易查的。如果错误的请求都集中在同一个参数值附近那么十有八九是数据没构造好或者参数引用错了。这个环节不需要太多高深技术但需要细致的对比。我见过查了一晚上最后发现是CSV文件里参数列对错了的情况。服务器日志是第二道防线。应用服务器的日志里会记录每个请求进入时的参数值和返回时的错误码前后对齐就能定位是哪一步出的问题。比如登录接口一直报用户不存在返回的错误码是1001日志里看请求带入的手机号是13800138001而这个手机号确实不在user表里那问题就回到数据准备或者参数化脚本上了。中间件状态排查是第三道。数据库连接池使用率、Redis命中率、消息队列堆积量这些指标能直观告诉你系统离瓶颈还有多远。当JMeter聚合报告TPS开始掉的时候去看监控曲线能定位到到底是哪一环先扛不住了。最后才是脚本本身。脚本问题往往是最隐蔽的。我遇到过一个案例压测稳定跑了几分钟突然错误率飙升监控数据全是正常的。最终发现是一个用户的CSV文件在某个位置混入了一个特殊字符导致后续所有请求都带上了这个脏字符串服务端解析失败。从那以后我在压测前多了一件事用脚本校验CSV文件的每行数据列数是否一致。5.3 关于数据分布的细节建议关于测试数据的均匀性和分布有一个不太容易注意但影响分析结果的细节。10万条用户数据如果全部集中在半年前注册则在压测注册时间筛选类接口的时候数据特征偏向单一索引选择性可能和线上不一致。更好的做法是按业务比例设定不同维度的分布。例如用户等级的分布我上面脚本里1~5级的权重是40%、30%、20%、8%、2%这基本符合普遍电商平台低等级用户多、高等级用户少的规律。用户注册时间分布在过去3年内均匀展开这样任何时间维度的统计分析接口压测都有足够的数据支撑。数据分布不合理带来的另外一个后果是缓存分布不均衡。如果100个线程全部集中在几个高等级用户的数据范围Redis缓存击穿了真实业务场景不会这么集中。所以造数时还是要重视分布的模拟别为了省事全部生成一模一样结构的数据。6. 从压测脚本到压测工程化的一些思考压完一轮、分析完报告一般人的动作是结束。但把这个项目完整复盘一遍我认为压测这件事可以做得比跑一次脚本更有价值。一个可以持续的改进方向是把压测脚本纳入版本管理。JMeter的jmx文件本质是XML文本完全可以放到代码仓库里做版本维护。脚本参数、CSV数据文件、环境配置都纳入版本管理换人接手、换环境重建都能秒级恢复。你的压测脚本会有更新的版本每次项目迭代完可以拿着同一套脚本再压一轮做对比。这比每次临时写脚本、用完就丢要科学得多。另一个经验是压测数据要和压测脚本分开维护。数据文件单独生成、单独管理脚本从数据文件读取。这样项目环境重建的时候造数脚本重跑一遍就行脚本本身不需要任何改动。还有一条比较重要的是压测报告不能只留JMeter的聚合报告截图把监控数据、日志切片、慢查询输出、调优前后的结果对比全部关联存档。等上线出了性能问题回溯压测记录能帮你快速判断是新版本引入的还是压测阶段就存在的历史问题。本项目做到后面我们把这些压测数据打包成了项目基线每次大版本迭代都会自动触一轮回归压测效果很好。结尾轻商城项目的压测做完以后我最大的体会是压测工作最核心的产物不是压测报告而是让人相信这份报告是可信的那套过程。数据是从哪来的、怎么构造的脚本是怎么参数化的、参数对不对场景是按什么比例设计的、线程组和CSV是怎么配合的这些细节一点点累积起来才让压测结果经得起推敲。那次调完用户表索引之后同一个接口在并发400的情况下95%响应时间从300ms降到70ms这个数字看起来漂亮但其实大部分功劳要记在准备阶段——没有那10万条真实分布的数据索引失效的问题根本不会暴露出来。最后再分享一个小技巧。我每次开始压测之前都会先开一个1分钟、并发10的小规模冒烟压测目的是验证脚本参数化、断言、数据文件这三样东西是否正确。等这轮跑完看一眼聚合报告确认没有报错、TPS符合预期再上真正的压测场景。这个习惯帮我省掉了不知道多少次压了半小时才发现脚本有问题的尴尬。希望这份流程对你有用有问题欢迎在评论区一起讨论。