4. 压测脚本设计:交易链路分析、参数化、关联与断言
好,咱们进入第四讲。这一讲,我打算把压测脚本设计的核心环节拆开揉碎了讲给你听。
很多人觉得写脚本就是录个回放,或者用工具拖拽一下。其实不然。脚本设计的好坏,直接决定了你的压测结果靠不靠谱。我见过太多团队,脚本写得一塌糊涂,跑出来的数据根本没法看。
说白了,脚本设计就是四个核心动作:链路分析、参数化、关联、断言。咱们一个一个来过。
4.1 交易链路分析:先画图,再动手
我个人习惯,拿到一个交易系统,第一件事不是写脚本,而是画图。画什么?画交易链路图。
你想想看,一笔交易从客户端发起,到最终落库,中间要经过多少节点?网关、认证中心、风控系统、订单中心、支付网关、清算系统……每个节点都可能成为瓶颈。
我在项目中遇到过,有个团队上来就对着登录接口猛压,结果发现登录没问题,下单接口却扛不住。为什么?因为他们根本没分析链路,不知道下单接口后面还调了三个外部服务。
这里我画了一张交易链路分析的示意图,你可以对照着看:
有了这张图,你就能清楚地知道:哪些环节是同步调用,哪些是异步消息。同步调用容易成为瓶颈,异步消息则要注意队列积压。
4.2 参数化:别让数据成为瓶颈
参数化,说白了就是让每次请求的数据都不一样。为什么?因为真实用户不会每次都提交同样的数据。
我见过最离谱的压测脚本,1000个并发用户全部用同一个用户名登录。结果认证服务直接报错,压测报告显示系统TPS为0。这不是系统不行,是脚本写得有问题。
常用的参数化方式有几种:
- CSV数据驱动: 从文件中读取参数,适合大量预置数据
- 函数生成: 用随机函数生成,适合时间戳、随机数等
- 数据库查询: 从数据库实时获取,适合需要保持数据一致性的场景
- 计数器: 按顺序递增,适合生成唯一ID
举个例子,在JMeter中,参数化一个用户ID可以这样写:
// 从CSV文件读取用户数据
${__CSVRead(/data/users.csv,0)}
// 或者用随机函数
${__Random(1000,9999)}
// 或者用时间戳保证唯一
${__time(yyyyMMddHHmmss)}
我个人习惯,对于交易系统,优先使用CSV数据驱动。因为交易数据往往有复杂的业务约束,预置数据可以保证合法性。我曾经在一个支付系统压测中,因为用了随机数生成订单号,结果生成了大量不合法的订单格式,导致压测结果完全失真。
4.3 关联:把前后请求串起来
关联,是压测脚本设计中最容易出错的地方。什么是关联?就是从一个请求的响应中提取数据,传给下一个请求使用。
交易系统里,关联无处不在。登录后拿到token,下单后拿到订单号,支付后拿到支付流水号。这些数据必须动态获取,不能写死。
我遇到过最坑的一次,是某个系统的token不是放在响应体里,而是放在响应头的Set-Cookie里。团队里的小伙子没注意,硬是从响应体里找,找了半天找不到。后来我一看,告诉他:「你看看响应头。」
常见的关联写法:
// 正则提取token
token = regex_extract(response, '"token":"([^"]+)"', 1)
// JSON提取订单号
orderId = json_extract(response, '$.data.orderId')
// 提取Cookie
sessionId = cookie_extract(response, 'JSESSIONID')
这里有个坑要提醒你:关联的提取表达式一定要写对。我建议你在写脚本之前,先用Postman或者curl手动调一次接口,确认响应结构。别像我之前那样,想当然地写了个正则,结果跑了一晚上压测,全是无效请求。
4.4 断言:别让错误数据蒙混过关
断言,就是检查响应结果对不对。很多人觉得断言不重要,反正压测看的是TPS和响应时间。但你想过没有,如果返回的都是错误数据,TPS再高又有什么意义?
我见过一个案例,压测报告显示TPS 5000,结果仔细一看,5000个请求全部返回了「系统繁忙,请稍后重试」。这5000的TPS,全是无效请求。
举个例子:
// 状态码断言
assert(response.status == 200, "HTTP状态码不是200")
// 业务码断言
assert(response.json().code == "0000", "业务返回码不是0000")
// 关键字段断言
assert(response.json().data.orderStatus == "SUCCESS", "订单状态不是SUCCESS")
我个人习惯,在压测脚本里至少加三个断言:
- HTTP状态码是否为200
- 业务返回码是否为成功
- 关键业务字段是否非空
这样基本能过滤掉90%的无效请求。剩下的10%,可以在压测结束后通过日志分析来排查。
4.5 实战中的脚本设计流程
好了,理论讲完了。咱们来捋一遍实战中的脚本设计流程。我一般按这个步骤来:
| 步骤 | 动作 | 说明 |
|---|---|---|
| 1 | 链路分析 | 画出交易链路图,标记依赖关系和瓶颈点 |
| 2 | 接口梳理 | 列出所有需要压测的接口,确定请求方式、参数、返回结构 |
| 3 | 参数化设计 | 确定哪些参数需要动态化,选择合适的数据源 |
| 4 | 关联设计 | 找出需要关联的数据,编写提取表达式 |
| 5 | 断言设计 | 编写断言规则,确保请求有效性 |
| 6 | 脚本调试 | 用少量并发跑一遍,检查日志和断言结果 |
| 7 | 脚本优化 | 根据调试结果调整参数、关联和断言 |
嗯,这个流程我用了很多年,基本没出过大的纰漏。你刚开始做的时候,可能会觉得步骤多,但习惯之后就会发现,每一步都是在为后面的压测结果负责。
最后说一句,脚本设计不是一蹴而就的。你可能会在压测过程中发现新的问题,然后回头修改脚本。这很正常。关键是保持思路清晰,每一步都问自己一句:「这个设计合理吗?」