4. 压测脚本设计:交易链路分析、参数化、关联与断言

好,咱们进入第四讲。这一讲,我打算把压测脚本设计的核心环节拆开揉碎了讲给你听。

很多人觉得写脚本就是录个回放,或者用工具拖拽一下。其实不然。脚本设计的好坏,直接决定了你的压测结果靠不靠谱。我见过太多团队,脚本写得一塌糊涂,跑出来的数据根本没法看。

说白了,脚本设计就是四个核心动作:链路分析、参数化、关联、断言。咱们一个一个来过。

4.1 交易链路分析:先画图,再动手

我个人习惯,拿到一个交易系统,第一件事不是写脚本,而是画图。画什么?画交易链路图。

你想想看,一笔交易从客户端发起,到最终落库,中间要经过多少节点?网关、认证中心、风控系统、订单中心、支付网关、清算系统……每个节点都可能成为瓶颈。

我在项目中遇到过,有个团队上来就对着登录接口猛压,结果发现登录没问题,下单接口却扛不住。为什么?因为他们根本没分析链路,不知道下单接口后面还调了三个外部服务。

核心思路: 先梳理出完整的交易链路,标记出每个节点的依赖关系、数据流向、以及可能的瓶颈点。

这里我画了一张交易链路分析的示意图,你可以对照着看:

典型交易链路分析图 客户端 API网关 认证中心 风控系统 订单中心 支付网关 清算系统 数据库 图例: 客户端 网关 认证 风控 订单 支付 清算

有了这张图,你就能清楚地知道:哪些环节是同步调用,哪些是异步消息。同步调用容易成为瓶颈,异步消息则要注意队列积压。

4.2 参数化:别让数据成为瓶颈

参数化,说白了就是让每次请求的数据都不一样。为什么?因为真实用户不会每次都提交同样的数据。

我见过最离谱的压测脚本,1000个并发用户全部用同一个用户名登录。结果认证服务直接报错,压测报告显示系统TPS为0。这不是系统不行,是脚本写得有问题。

注意: 参数化不仅仅是换几个数字。要考虑数据之间的业务关联。比如下单时,商品ID、用户ID、优惠券ID必须是一套合法的组合。

常用的参数化方式有几种:

  • CSV数据驱动: 从文件中读取参数,适合大量预置数据
  • 函数生成: 用随机函数生成,适合时间戳、随机数等
  • 数据库查询: 从数据库实时获取,适合需要保持数据一致性的场景
  • 计数器: 按顺序递增,适合生成唯一ID

举个例子,在JMeter中,参数化一个用户ID可以这样写:

// 从CSV文件读取用户数据
${__CSVRead(/data/users.csv,0)}

// 或者用随机函数
${__Random(1000,9999)}

// 或者用时间戳保证唯一
${__time(yyyyMMddHHmmss)}

我个人习惯,对于交易系统,优先使用CSV数据驱动。因为交易数据往往有复杂的业务约束,预置数据可以保证合法性。我曾经在一个支付系统压测中,因为用了随机数生成订单号,结果生成了大量不合法的订单格式,导致压测结果完全失真。

4.3 关联:把前后请求串起来

关联,是压测脚本设计中最容易出错的地方。什么是关联?就是从一个请求的响应中提取数据,传给下一个请求使用。

交易系统里,关联无处不在。登录后拿到token,下单后拿到订单号,支付后拿到支付流水号。这些数据必须动态获取,不能写死。

关键点: 关联要处理三种情况——正则提取、JSON提取、以及Cookie/Header提取。

我遇到过最坑的一次,是某个系统的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")

我个人习惯,在压测脚本里至少加三个断言:

  1. HTTP状态码是否为200
  2. 业务返回码是否为成功
  3. 关键业务字段是否非空

这样基本能过滤掉90%的无效请求。剩下的10%,可以在压测结束后通过日志分析来排查。

4.5 实战中的脚本设计流程

好了,理论讲完了。咱们来捋一遍实战中的脚本设计流程。我一般按这个步骤来:

步骤 动作 说明
1 链路分析 画出交易链路图,标记依赖关系和瓶颈点
2 接口梳理 列出所有需要压测的接口,确定请求方式、参数、返回结构
3 参数化设计 确定哪些参数需要动态化,选择合适的数据源
4 关联设计 找出需要关联的数据,编写提取表达式
5 断言设计 编写断言规则,确保请求有效性
6 脚本调试 用少量并发跑一遍,检查日志和断言结果
7 脚本优化 根据调试结果调整参数、关联和断言

嗯,这个流程我用了很多年,基本没出过大的纰漏。你刚开始做的时候,可能会觉得步骤多,但习惯之后就会发现,每一步都是在为后面的压测结果负责。

避坑指南: 我曾经因为跳过了「脚本调试」这一步,直接上大规模压测。结果跑了两个小时才发现关联写错了,所有请求都用的同一个token。那两小时的数据全废了。所以,调试这一步,千万别省。

最后说一句,脚本设计不是一蹴而就的。你可能会在压测过程中发现新的问题,然后回头修改脚本。这很正常。关键是保持思路清晰,每一步都问自己一句:「这个设计合理吗?」