
先说一个我压测时经常遇到的怪现象脚本在本地回放毫无异常响应时间曲线漂亮得像教科书事务成功率 100%但把同样的脚本丢到预发环境一跑业务方一看结果就皱眉头——你这个和线上真实情况差太远了。问题往往不在脚本逻辑而在一个看起来毫不起眼的设置思考时间Think Time。很多从功能测试转过来的人第一次接触 LoadRunner 时最不重视的就是思考时间。有人干脆觉得它碍事“我压测嘛就是要模拟并发把服务器压垮搞什么停顿直接连发不就行了”这种想法挺危险的。思考时间这东西表面上只是模拟用户“发呆”的时间实际上它决定了你的压测数据能不能代表真实生产环境。今天这篇文章我就围绕思考时间的设置技巧从原理、脚本、运行时配置到场景叠加完整讲一遍。这篇文章既适合刚上手 LoadRunner、正在为怎么让脚本更真实发愁的新手也适合已经跑过几轮压测、但总感觉结果不对味、想排查是不是思考时间设置有问题的人。我会把实操步骤、参数选择逻辑、还有我自己踩过的坑都写出来尽量让每个人都能直接照着试。1. 思考时间到底在模拟什么1.1 一次“假”压测事故先讲个印象比较深的例子。某系统做一个大促前的容量评估负责压测的同事写了个很简单的脚本打开首页查询商品列表加购物车提交订单。脚本里干干净净一个思考时间都没有。压测跑了 30 分钟并发 200平均响应时间 1.8 秒事务成功率 99.9%看起来一切正常。然后放了 200 个真实用户进去做试用结果登录页直接卡死数据库连接池打满商品详情接口大面积超时。为什么会这样因为真实用户不会像脚本那样一秒钟内连续完成四个操作。真实用户先要扫一眼页面想一想价钱大概停顿 5 到 10 秒再点下一个按钮。这中间的停顿就是思考时间。没有它的脚本相当于把 200 个人的日常操作节奏压缩成了 200 个人同时无脑连点服务器承受的压力完全不是同一码事。用无思考时间的脚本做出来的“没问题”其实捕获不了真实环境的瓶颈。这个案例不是个例。后来我复盘时发现压测脚本里缺失的思考时间导致请求密度被严重放大数据库连接池被频繁占用而这部分压力在线上的真实用户行为里根本没有那么密集。简单说思考时间是连接“虚拟用户”和“真实用户”的桥梁桥没搭好数据自然失真。1.2 思考时间不是“减速器”而是“真实性调节器”很多人把思考时间理解成“脚本减速器”觉得它存在的唯一意义就是把脚本跑慢一点。这么说也不全错但更准确地说思考时间是在调整负载模型中用户行为的真实性。它模拟的不只是“翻页时停顿”还包括用户阅读信息、核对表单、犹豫输入甚至切出去看微信再切回来这类行为的时间开销。用生活里的例子类比早高峰地铁站进站闸机的吞吐量取决于刷卡乘客之间的间隔。有人刷卡快有人磨磨蹭蹭找卡有人到闸机前才翻包。单独看每一个人刷卡只花 0.5 秒但整个进站队列的疏解速度完全是由这些“磨蹭时间”决定的。压测也是这样服务器能承受多少并发不是看每个请求多快而是看用户请求之间的平均间隔有多大。所以说思考时间是负载模型里的关键变量。它不是为了让脚本变慢才加进去的而是为了还原并发请求在时间轴上的分布形态。没有思考时间的脚本就像一个把所有乘客都压缩成“手拉手刷卡”的模型看着顺畅实际失真。1.3 思考时间设置不当的三种典型后果踩过的坑多了总结下来基本就三类。第一类完全忽略思考时间。脚本里没有任何停顿所有请求像机关枪一样连发。这种方式会让服务器资源利用率虚高接口响应时间也被异常拉长因为单用户请求频率高到超出正常人类操作范围数据库连接池、线程池的排队机制会被无意义的并发压到极限。最后得出来的 TPS、平均响应时间、错误率基本不能反映真实用户体验。第二类思考时间放错了位置。脚本里确实有思考时间但写在了事务内部。这样会导致事务响应时间虚高。举例来说一个查询动作实际处理只要 0.8 秒但因为把 15 秒的思考时间包进了事务里统计出来的“查询响应时间”就成了 15.8 秒。压测报告拿给业务方看对方会觉得系统慢得离谱可实际上慢的不是系统是你的统计口径出了问题。第三类思考时间固定不变。脚本里写了 10 秒所有人都 10 秒每个迭代都是 10 秒。这样会导致请求呈现出一种很奇怪的“同步效应”——大家一起同时发出请求然后一起同时思考再一起同时发起下一轮。这种负载不是模拟真实用户而是模拟一群被训练过的士兵。思考时间缺乏随机性会让压测结果忽高忽低难以定位真正的瓶颈。2. 三个必须搞懂的概念事务、运行时设置、作用层级2.1 思考时间在事务内还是事务外位置敏感度在 LoadRunner 脚本里思考时间函数的位置非常敏感它直接决定你的响应时间统计口径。很多人刚开始搞不清楚以为事务只是用来圈定操作范围的其实它同时也是统计时间的边界。先看两种写法。// 推荐做法思考时间在事务外 lr_start_transaction(查询商品列表); web_url(list, URLhttp://xxx/api/list, Resource0, RecContentTypeapplication/json, LAST); lr_end_transaction(查询商品列表, LR_AUTO); lr_think_time(10);// 不推荐思考时间被包进事务里 lr_start_transaction(查询商品列表); web_url(list, URLhttp://xxx/api/list, Resource0, RecContentTypeapplication/json, LAST); lr_think_time(10); lr_end_transaction(查询商品列表, LR_AUTO);第一种写法里事务统计的是从发起请求到收到响应的时间服务器处理速度是多少事务时间就是多少不掺水分。第二种写法里lr_think_time 夹在 start 和 end 之间事务时间就包含了 10 秒的思考时间。所以我有个习惯凡是脚本里插入了 lr_think_time我就会习惯性地检查一下它跟事务的 relative 位置。通常思考时间放在事务外特殊情况才放事务内——例如你想测试“端到端业务体验时长”那就另说但报告里必须把口径注明清楚否则别人看到一份混杂着思考时间的事务数据根本没法用。2.2 运行时设置里的三个选项占用、回放、缩放脚本里写了 lr_think_time 只是一回事真正决定它能不能生效的是运行时设置Runtime Settings。路径在Vuser - Run-Time Settings - General - Think Time。这里一共三个选择我来逐个说选项一Ignore think time忽略思考时间。选择它以后回放时无论脚本里写了多少 lr_think_time都会被直接跳过。脚本会变成纯压力模式每个请求发完立刻发下一个。这个选项适合做压力测试因为它最大化请求密度检验系统在“非正常人类”频率下的极限表现。选项二Replay think time as recorded按录制值回放。选择它以后脚本里的 lr_think_time 会原样生效录制时是 8 秒就是 8 秒15 秒就是 15 秒。这是最接近真实用户行为的方式适合容量评估、基准测试。缺点是偶尔录到特别大的思考时间比如用户中途离开接了 5 分钟电话这会导致脚本执行时间很长影响整体压测效率。选项三Modify think time按比例修改。这是最灵活的方式可以在不回放脚本的情况下统一把录制思考时间乘以一个百分比。比如设 50%那所有 10 秒的思考时间就变成 5 秒设 200%就变成 20 秒。这在调整用户操作快慢的模拟上有奇效。后面第 4 章我会详细说怎么用。2.3 不同测试目标下的选择逻辑这三个选项不是随便点的不同的压测目标对应不同配置。我按自己的经验整理了一个选择逻辑压测目标思考时间配置理由极限压力测试Ignore think time短时间产生最大请求密度找系统上限容量规划/基准测试Replay as recorded模拟真实用户节奏结果可复现可对比混合业务负载评估Modify 25%~50%模拟业务高峰时用户操作节奏加快长时间稳定性测试Replay as recorded 上限限制既保持真实感又避免个别超长思考时间拖长整个测试上线前全链路验证Modify 80%~100%贴近真实留出少量余量避免低估压力这里要特别提醒运行时设置里选的是 Ignore脚本里写再多 lr_think_time 也是废的。我见过有人辛辛苦苦在脚本里插了二十几个 lr_think_time结果回放一看全部被跳过因为运行时设置一直是默认的 Ignore。所以每次回放前我一定先看一眼运行时设置里 Think Time 到底选的哪个。3. 脚本里的思考时间实操从录制到参数化3.1 录制时如何自动生成规范的思考时间自动化录制是思考时间最直接的来源但默认配置不优化的话录出来的思考时间往往杂乱无章。录制时LoadRunner 会在 Vuser 的脚本里自动插入 lr_think_time 函数但前提是你在录制选项中打开了对应的开关。路径Tools - Recording Options - General - Think Time。录制选项里有几个阈值需要理解。第一个是“Record think time”开关默认开启。开启后LoadRunner 会检测两次请求之间你停顿了多久。第二个是“Think time 阈值”一般默认是 2 秒到 5 秒。它的意思是如果两次请求之间的停顿超过 2 秒就把这部分时间记录为思考时间如果间隔在 2 秒之内LoadRunner 不会把它当成思考时间而是当作正常的页面加载等待。为什么这个阈值很重要如果阈值设得太小比如 0.5 秒几乎每次操作之间的微小停顿都会被记为思考时间脚本里会到处是 lr_think_time有的是 0.3 秒、0.8 秒非常碎片化回放效率也低。如果阈值设得太大比如 15 秒那么大多数真实停顿都不会被识别录制出来的脚本几乎没有思考时间压测数据自然偏积进。我一般会把阈值设为 1 到 3 秒既能过滤掉页面加载的噪声又能保留主要操作间的阅读停顿。录制完成后查看脚本你会看到类似这样的代码web_url(login, URLhttp://xxx/login, LAST); lr_think_time(6); web_url(dashboard, URLhttp://xxx/dashboard, LAST);这里的 6 秒就是你点完“登录”按钮后眼睛扫一眼页面、准备点“查看仪表盘”之前的真实停顿。这种停顿特别值钱因为它不是人为拍脑袋编出来的而是真实业务操作中记录下来的。3.2 手动插入 lr_think_time 的语法和位置技巧录制只是起点更多时候我们需要手动调整思考时间。先看函数语法int lr_think_time(int time_in_seconds);参数单位是秒整数或浮点数都可以。比如lr_think_time(10); // 固定等待 10 秒 lr_think_time(0.5); // 等待 0.5 秒常用于微停顿 lr_think_time((int)var); // 使用变量作为思考时间大多数人不知道的是lr_think_time 不是 C 语言标准函数它是 Vuser 运行环境提供的一个专用指令。它能不能生效完全取决于运行时设置。把 lr_think_time 当成 sleep 来用是最常见的新手失误。sleep 只会让脚本傻等而 lr_think_time 在“忽略思考时间”模式下会被直接跳过它扮演的是“可配置的行为模拟”。手动插入位置我推荐这几个节点登录成功后模拟用户看页面再点下一步的停顿同一个事务流程内部的步骤之间模拟表单填写与确认前的犹豫跨模块切换之间比如从“查询”切换到“报表”模拟用户思考是否要换功能当然不要在每个请求后面都插思考时间不然脚本会变得很臃肿。通常一个业务流程里选 3 到 5 个关键操作点作为停顿节点就够了。3.3 让思考时间“活”起来随机、参数化与分组固定思考时间会让负载模型显得很“机械”。想象一下50 个用户同时思考 10 秒然后同时点按钮再同时思考 10 秒服务器看到的请求就是一阵一阵的脉冲而不是一条平滑的曲线。这种脉冲式负载会让性能瓶颈时有时无测试结果方差很大定位问题特别痛苦。解决办法就是让思考时间带随机性。最简单的做法用 C 语言的 rand() 函数配合一个范围lr_think_time((rand() % 6) 5); // 生成 5 到 10 秒的随机思考时间这样每个虚拟用户每次迭代停顿的时间都不一样请求分布在时间轴上就自然散开了更符合真实情况。LoadRunner 里的 Vuser 脚本在每次迭代时会重新调用 action乱数种子如果不做处理rand() 可能在不同迭代里生成相似序列。所以在脚本初始化里加上 srandsrand((unsigned)time(NULL));这样每次迭代的随机序列都有差异。如果不加你可能会发现虽然公式写了随机但每次跑出来都是一串相同的“随机数”等于白写。另一个更贴近真实场景的方式是使用参数文件。比如登录操作前停顿 3~5 秒查询操作前停 8~12 秒下单确认前停 15~20 秒。我通常会把这类分布做成参数文件然后用 lr_eval_string 读取// 参数文件 ThinkTimeParam.dat 中的值来自真实用户操作间隔统计 lr_think_time(atoi(lr_eval_string({ThinkTimeParam})));这种方法的优势在于有据可依。当你手头有生产环境的操作日志可以统计出各业务步骤之间的时间间隔分布然后直接灌入参数文件脚本的“演技”瞬间提升一个档次。再进阶一点可以根据业务分组设置不同思考时间。比如if (strcmp(lr_eval_string({BusinessType}), Browse) 0) { lr_think_time((rand() % 4) 2); } else if (strcmp(lr_eval_string({BusinessType}), Order) 0) { lr_think_time((rand() % 10) 5); } else { lr_think_time((rand() % 15) 10); }这样不同业务类型的操作链路上思考时间差异就体现出来了负载模型层次感更强。3.4 回放前必须确认的运行时配置脚本里插入了思考时间是不是就可以直接回放了不行还要过一道运行时设置的门。我自己的固定检查流程是这样的先选 Windows - Run-Time Settings找到 Think Time 的卡片确认一下当前选的是哪个模式。如果脚本里有 lr_think_time那么“Ignore think time”绝对不能选否则脚本中的思考时间会被跳过。如果选了“Modify think time”那么确认一下百分比是不是你想要的。如果脚本里写了随机函数还要确认一下“Limit think time to”这个选项没有误开因为这个选项会硬性截断思考时间上限。比如你脚本里随机生成 40 秒的思考时间但 Limit 设了 10 秒那么实际生效的只有 10 秒。还有一个细节脚本回放时可以打开 Replay Log 里的 Think Time 信息观察实际生效的思考时间是多少。具体操作是Vuser - Run-Time Settings - Log勾选“Extended log”里的“think time”选项。回放日志里会输出类似Action Think Time: 8.0 seconds的记录用来确认你的思考时间配置真的生效了。4. 场景中的叠加与微调思考时间的加减法4.1 Controller 里单独设置思考时间的隐藏逻辑很多人在 Vuser 脚本里调好了思考时间跑到 Controller 里一加载发现效果和本地回放完全不一样。这里藏着一个很重要的逻辑Controller 创建运行时设置时会用它自带的一套设置覆盖脚本自带的设置。也就是说你在 VuGen 里改的 Run-Time Settings到了 Controller 里不一定生效。正确做法是在 Controller 的“运行设置”里找到 Think Time 选项卡确认里边的三个选项。Controller 里的操作路径是场景 - 运行时设置 - 通用 - 思考时间。这里还有一个被忽略的点不同脚本组可以分别设置思考时间。比如压测场景里有两个脚本一个模拟登录/查询的常规用户一个模拟恶意刷接口的压力来源。常规用户脚本保留思考时间压力来源脚本直接 Ignore。这种分组策略比把所有脚本统一设置成同一种思考时间要精细得多。4.2 按百分比缩放思考时间的实际应用Modify think time 不是摆设它是我在调整压测强度时的利器。原理很简单把录制时的思考时间统一乘以一个百分比形成不同“节奏”的用户。举个例子。某系统在录制时一次完整购物流程的思考时间总和是 45 秒。现在要做三组数据对比模拟正常用户保持 45 秒用 Replay as recorded模拟赶时间的用户把思考时间压到 30%即 13.5 秒用 Modify 30%模拟悠闲用户把思考时间翻倍到 200%即 90 秒用 Modify 200%在同一个场景里我可以生成三个 Vuser 脚本组使用同一个脚本但分别设置不同的 Modify 比例。这样一组压测里同时存在“着急型”、“普通型”、“慢悠悠型”用户请求在时间轴上的分布会更复杂也更接近生产环境。这个方案特别适合大促压测把多个业务线放一起部分用户思考时间短、操作密集部分用户思考时间长、操作稀疏整体更加贴近真实人群的行为差异。4.3 一组对比数据思考时间如何影响 TPS 与资源光讲理论没意思给一组我实际做过的对比参考数据。同一个脚本同一个并发数 100只是修改思考时间配置结果差异很明显配置并发数平均思考时间TPS平均事务响应时间服务器 CPU 峰值Ignore think time10009501.2s88%Replay as recorded100~10s1050.9s42%Modify 30%100~3s3100.9s56%看到这个数据你可能会问“TPS 从 950 掉到 105是不是系统变弱了”不是。TPS 下降是因为每个 Vuser 在单位时间内发出的请求变少了但每个请求的处理能力和事务响应时间反而更接近真实。直接用 Ignore 跑出来的 950 TPS看似漂亮实际是 100 个用户在无脑连点这种场景在正常业务里根本不存在。重点是Modify 30% 这一组TPS 310、CPU 56%在负载高峰下既保住了一定的请求密度又没有让资源被打满。这就是思考时间作为一个“负载调节阀”的价值它可以通过乘一个百分比把压力调整到一个更贴合业务预期的水平。5. 常见问题排查与经验速查5.1 为什么设置了思考时间但 TPS 没变化这是被问到最多的问题。脚本里明明加了 lr_think_time回放日志也显示有思考时间但 TPS 就是纹丝不动。排查顺序我建议按下面这个清单来1024 首先检查运行时设置是不是 Ignore think time。如果是把它改成 Replay 或 Modify问题马上解决。其次检查是否在 Controller 里覆盖了脚本设置。前面说过Controller 的运行时设置会覆盖 VuGen 里的设置所以即便 VuGen 里是好的Controller 里也可能是“忽略”。再看脚本里 lr_think_time 放在哪。如果放在了 if/else 分支里但该分支没有执行思考时间自然不生效。建议在回放日志里搜“think time”关键字看看它真的执行了没有。最后看 Vuser 数。如果你只开了 1 个 Vuser思考时间的影响很难被观察到至少需要 20 个 Vuser才能看到 TPS 曲线有变化。5.2 思考时间到底设多少合适这是个没有标准答案的问题但我有几个实操建议。没有历史数据时直接用录制值。真实操作中你停顿了多少秒就是最基础的依据。之后做一轮小规模压测再对比响应时间和资源利用率看看是否符合预期。有生产数据时打开业务日志统计用户两次关键操作之间的大小时间间隔。计算均值、中位数、P90、P95然后把数据做成参数文件贴进脚本。如果生产日志显示 50% 的用户在查询商品后 5 到 15 秒内才会点击详情那脚本里的思考时间就应该覆盖这个区间而不是偷懒写一个固定 10 秒。还有一个粗暴但有效的估算方式对比真实用户完成一个业务流程的总时间和脚本无思考时间的事务总耗时。例如真实用户完成“登录 - 搜索 - 查看详情 - 下单”需要 60 秒而脚本里三个事务合计只花 5 秒那么剩下的 55 秒应该分配到各步骤之间的思考时间上。至于每个步骤分配多少参考生产日志的间隔分布。5.3 移动端和接口压测要不要思考时间这个问题的答案要分场景。移动端的用户行为和 Web 端差别很大移动端用户阅读停留时间普遍更短点击更频繁操作前停顿明显降低而且还有网络切换、弱网等待等因素存在。如果直接拿 Web 端录制的思考时间套到移动端压测脚本上数据并不会准确。建议对移动端单独录制单独统计操作间隔。接口压测的特殊之处在于很多场景关心的是服务端的极限吞吐能力而不是模拟端到端体验。对于这类目的完全可以不用思考时间直接 Ignore把压力拉满。但如果接口调用链是模拟业务级操作流比如“用户注册 - 绑定卡 - 下单 - 支付”那么步骤间思考时间还是有必要的否则整条链路的请求频率会远超真实比例。5.4 思考时间使用的六大误区整理了这么久的项目我把最常见的误区列一下对照排查效率很高。误区一思考时间设得越小越能加压。这是对“压力”的误解。压测的压力强度由并发用户数和请求频率共同决定。过小的思考时间虽然会提升请求频率但会导致失真最终数据很难指导容量决策。误区二把 lr_think_time 当成普通 sleep。两者最大的区别就是 lr_think_time 会受运行时设置控制而 sleep 不会。控制力强是优点但也意味着你必须清楚记得每个配置项的状态。误区三一套思考时间打天下。不同业务操作、不同用户群体、不同时段思考时间差异可能非常大。一个固定值很难满足所有场景。建议配合参数文件或随机函数做分层模拟。误区四思考时间误入事务边界。这一点前面讲过了一定要把关事务内外保证指标含义清晰。误区五只在 VuGen 里改设置不在 Controller 里验证。很多压测数据不一致的问题都出在 Controller 覆盖了脚本配置。每次建场景时手动检查一遍运行时设置特别是 Think Time 选项卡。误区六想删掉思考时间就手动删除函数。没必要。在运行时设置里选 Ignore 就可以完全忽略而且后续想恢复时只要把配置改回来即可脚本还保持完整可追溯性更好。六、最后再聊一点个人习惯压测项目做多了我现在接新项目的第一个动作不是看脚本怎么写而是先问业务方“你们真实用户完成这个流程平均花多久”这个问题听着简单却最考验对业务的理解。思考时间就是这个时间账本里的最大变量它比任何代码优化都更影响测试结果的可靠程度。回看我自己早年的压测脚本经常为了把 TPS 跑高故意把思考时间调小数据虽然好看了可上线后真实用户体验完全没有达到预期。后来才慢慢悟到压测不是为了得到一个“性能很好”的结论而是为了提前知道系统在真实用户行为下的表现。思考时间这个看似不起眼的参数决定了你得到的结论是不是经得起业务方追问。最后再分享一个小技巧压测结束后保留一份思考时间配置记录连同测试结论一起归档。这样当下一次压测出现结果波动时可以先比对这个配置快速判断是思考时间设置变了还是代码或环境变了。细节记录到位踩坑成本就能小一大半。