购物系统做性能压测很多团队一上来就打开JMeter录个脚本加个并发线程组点一下运行然后盯着聚合报告看吞吐量。这种玩法不能说完全没用但对于浏览商品→添加购物车→提交订单→支付这种多链路、带状态流转、牵扯库存和金额的系统直接压单个接口压完也回答不了系统到底能撑住多少用户在买这个问题。这篇文章我从怎么设计的角度把购物系统压测从场景建模、数据设计、脚本落地、加压策略到监控定位、经典雷区完整拆一遍。不谈空理论只讲能直接用的方法。1. 先理清购物系统的压测目标和链路边界1.1 购物系统压测要回答的三个核心问题购物系统的性能压测本质上不是压一个接口而是压一条业务链路。你在设计压测方案之前得先明确这次压测到底要回答什么问题。按照我的经验基本逃不开下面三个容量问题系统在现有的服务器配置、中间件规格下能支撑多大的业务量具体表现就是每秒能处理多少请求能同时支撑多少活跃用户在线购物。稳定性问题系统在持续高压下运行一两个小时甚至更久会不会出现内存泄漏、连接池耗尽、Full GC频繁、消息堆积这类缓慢恶化的问题。峰值应对问题大促、秒杀、新品首发这种瞬间流量洪峰场景下系统会不会被直接打垮限流、降级、熔断这些保护措施能不能按预期生效。这三个问题需要的设计方案完全不同。容量问题适合做阶梯加压探测稳定性问题需要长时间恒定压力跑持久化测试峰值应对则需要突发加压模型来模拟秒杀瞬间的流量形态。我见过不少团队拿同一个脚本改个并发数就当所有问题都测了这是不对的。1.2 浏览、加购、支付三条链路的本质差异购物系统的主链路看起来简单就是浏览商品→加购→下单→支付但每段请求的技术特征差别非常大压测设计时必须有区别地对待。浏览商品链路包括商品列表、商品详情、搜索等接口。这是典型的读多写少场景负载大头在查询和缓存。压测重点是缓存命中率、CDN命中率、数据库读吞吐、分页查询的性能。这个链路的下游依赖相对简单瓶颈通常出现在缓存设计和SQL效率上。加购链路包括加入购物车、修改购物车数量、选中商品等接口。看似是一个轻量写操作但它往往涉及购物车服务、商品服务、库存服务之间的调用。需要注意的是一写多读场景下的数据一致性问题比如并发加购时购物车条目计数是否准确、库存预占是否超卖、Redis热点key是否被打爆。支付链路这是最特殊的一段。它不只是发起支付这一个动作还包括订单创建、库存扣减、支付单生成、支付回调处理、订单状态流转、出库通知等一系列编排动作。整个过程跨越多个微服务还有大量异步消息和分布式事务参与。压测支付链路时如果只压发起支付这个接口基本等于没压。另外一个关键差异是浏览和加购可以很轻松地构造并发但支付链路涉及金额和库存压测时要非常小心脏数据问题。所以支付链路我不建议直接压真实支付网关走沙箱环境或者Mock掉第三方回调是更稳妥的做法。1.3 压测范围怎么划哪些必压、哪些可以Mock很多人在压测设计阶段就栽在范围划不清上——什么都想压结果什么都压不准。我的划分原则很简单凡是自己系统内的服务、中间件、数据库全部纳入压测范围凡是外部依赖除了支付网关的沙箱环境其余优先Mock或者用测试替身。比如短信服务、邮件通知、物流查询这类外部依赖它们不是核心购买链路的一部分但在压测时会被大量调用如果真实接通不仅会拖慢压测本身还会把外部服务的限流阈值打爆导致压测结果失真。这类依赖直接Mock掉。但库存、价格、订单、支付单这些核心域服务必须真实参与压测。因为购物系统的性能瓶颈往往就藏在这些服务之间的数据交互里。2. 场景建模如何把用户动线转成可量化的压测模型2.1 业务比例怎么定浏览、加购、支付的漏斗模型场景建模是整个压测设计里最容易拍脑袋的环节。很多人直接把三个接口按1:1:1的比例设计并发这种设计没有任何业务含义。真实用户在购物系统里的行为是呈漏斗形态的——大量用户浏览一部分人加购更少的人支付。我一般这样定业务比例先看后台真实的埋点转化数据如果线上能看到直接用线上数据。如果没有线上参考就按行业经验值设计比如浏览:加购:支付100:10:1。也就是说每100个并发请求里大约有100个浏览类请求、10个加购请求、1个支付请求。这里有个非常关键的点这个比例是请求次数比例不是并发用户数比例。也就是说在压测脚本里一个虚拟用户VU在一次完整业务循环中可以产生多个浏览请求、一个加购请求、一次支付请求。这样做的好处是压测的流量模型更接近真实情况。混合场景的请求比例需要通过压测工具的控制来保证。比如JMeter里可以给不同的事务控制器设置不同的权重或者用Throughput Controller来控制各类请求的吞吐占比。更精细的做法是直接在脚本里用if条件判断让虚拟用户按概率分布走不同的业务分支比如90%的用户只浏览9%的用户加购就离开只有1%的用户走完整个购物流程。2.2 从业务目标倒推并发用户数压测场景设计的第一个拦路虎是并发用户数到底设置多少最忌讳的做法是拍脑袋定一个5000就开压。正确的方式是从业务目标倒推。我常用的换算方式是这样的假如你希望系统能够支撑日均100万订单而一天的高峰流量集中在4个小时内假设这4小时的订单量占全天订单量的80%二八原则那么高峰4小时订单总量 100万 × 80% 80万单每秒订单数 80万 ÷ (4 × 3600秒) ≈ 55.6 TPS这是订单接口的目标吞吐量。支付链路参照同样的数值设计。然后加购请求按转化率反推假设加购到支付的转化率是10%则加购接口的TPS 556 TPS。继续反推浏览接口假设浏览到加购的转化率也是10%则浏览接口的TPS 5560 TPS。拿到TPS目标后再换算成并发用户数。这里涉及一个关键参数——思考时间Think Time。因为真实用户每操作一步中间都有人眼看、手点、犹豫的时间这个时间会让单用户的请求频率大幅下降。估算并发用户数的公式是并发用户数 目标TPS × 单用户平均请求间隔秒 正在执行请求的用户数举个例子目标是浏览接口5560 TPS每个用户平均每5秒发一个浏览请求含思考时间那么浏览链路的并发用户大约需要 5560 × 5 27800 个虚拟用户。如果这个数字太大测试机扛不住可以在脚本里适当缩短思考时间同时用更高的单用户请求频率来等效模拟但要注意这种方式对服务器造成压力的形态会和真实情况有偏差——请求更密集但连接保持的特征差了。2.3 单链路基准与混合链路容量的两段式策略我强烈建议压测策略分成两个阶段。第一阶段是单链路基准测试也就是分别对浏览、加购、支付三条链路单独加压。这一步的目标是摸清每个环节的单点容量上限比如浏览链路在缓存完全命中的场景下能跑到多少QPS支付链路的数据库写吞吐上限是多少。这类数据是后续调优的基础也是排查混合链路问题时定位瓶颈的参照物。第二阶段是混合链路容量测试按前面算好的业务比例同时施压。这个阶段的核心观察点是三条链路叠加后系统整体容量是三者之和还是被某一条链路拖垮在实际压测中经常出现的情况是单个链路都很健康但混合施压后某个链路的响应时间急剧恶化这往往意味着某类共享资源数据库连接池、线程池、Redis连接已经成了全局瓶颈需要从全局视角重新规划容量。混合链路压测跑完你手上才算有了一份真正能指导容量评估的报告。3. 数据设计压测结果真实性的隐形决定因素3.1 用户数据池登录态与Token批量构造压测购物系统的第一个坑在用户数据这里。很多人压测时所有虚拟用户共用同一个账号登录这种做法在小并发下看不出问题并发一高就彻底失真。原因很简单如果系统按用户维度做缓存比如购物车缓存、用户Session缓存一个账号下面挂的数据会被所有线程共用热点Key问题被无限放大测出来的结果会显著差于真实情况。正确做法是构造一个足够大的用户数据池。数据池的规模至少要覆盖并发用户数的1.5到2倍最好是可持续扩充的。每个用户有独立的账号、Token、收货地址、历史订单。压测脚本运行时每个虚拟用户从数据池里取一个独立用户用完归还或轮换。如果系统有登录态有效期限制要注意Token过期的问题。压测时长动辄一两个小时Token可能中途失效脚本需要有自动重新登录并刷新Token的机制。我的做法是在JMeter中用JSON提取器解析登录接口返回的Token存入属性变量然后在后续请求中通过属性引用并且用一个定时器周期性检查有效期。3.2 商品池与库存水位的设计细节商品数据的设计比用户数据更容易被忽略但它对加购和支付链路的压测结果影响极大。我在设计商品池时首先考虑的是热点集中度。真实线上流量通常呈二八分布——20%的热门商品承接了80%的访问量。所以压测数据不能平均分配请求到所有商品上否则商品详情缓存、库存扣减等环节的压力模型会严重失真。我在商品池里按比例准备了三类数据少量高频商品比如100个SKU覆盖60%请求模拟爆款商品集中压力验证热点场景。中等数量商品比如1000个SKU覆盖30%请求模拟一般商品。大量长尾商品比如10000个SKU覆盖10%请求模拟长尾流量。库存水位也很讲究。如果库存量远大于并发请求数永远不会出现超卖或扣减失败等于没测到真正的争抢。我一般将参与压测的热门商品库存设置成大约是目标并发数的10%到30%这样压测过程中会有真实的库存扣减竞争产生能覆盖到库存不足导致加购/下单失败的业务分支。此外压测数据要尽量保证商品价格的多样性。支付链路会涉及金额计算全部商品都是同一价格会让金额计算的精度问题无法暴露。3.3 订单数据膨胀与压测数据清理这是购物系统压测里最容易被轻视的一个问题。压测只要跑起来订单表、支付流水表、购物车表一天就能多出几十万甚至上百万条数据。数据量膨胀之后数据库的索引效率、分页查询性能都会发生变化。有两个典型的副作用一是压测过程本身造成的数据膨胀会反过来影响压测结果比如压测跑到后半段订单表从10万行涨到了80万行同样是查询订单列表的接口响应时间就会明显变慢这不一定代表系统性能变差而是数据基数变了。二是脏数据污染线上环境。如果压测环境跟开发环境共用数据库压测产生的垃圾订单和支付流水会让开发调试时异常困惑。所以压测开始前记录主要业务表的行数基线。压测结束后执行数据清理脚本按压测标记位删除测试数据。在设计订单号时预留压测标识字段或使用统一前缀方便清理和识别。4. 脚本落地的关键细节从登录到支付结果验证的完整闭环4.1 Token、商品ID、订单ID的关联传递压测脚本不是把接口一个个打一遍就行最核心的是把请求之间的动态参数串联起来。购物系统尤其复杂因为链路中间会产生大量运行时数据比如登录Token、商品SKU ID、购物车ID、订单ID、支付单号等。我的脚本设计一般这样处理登录接口返回的Token/Session ID通过JSON提取器或者正则提取器提取写入JMeter的变量。商品列表接口返回的商品ID列表随机选一个用于后续加购请求这个随机很重要否则所有虚拟用户都加购同一个商品热点被无限放大。加购接口返回的购物车条目ID作为下一步选中/下单的入参。下单接口返回的订单ID传给支付接口支付接口返回的支付流水号用于后续的对账查询接口。这一整套关联关系做下来脚本才真正模拟了一个用户完整的购物行为。如果直接在脚本里写死商品ID和订单号测出来的只是接口的孤立性能不是业务链路的性能。4.2 支付网关Mock与沙箱环境的取舍支付环节的压测是购物系统压测里争议最多的地方。真实支付网关是外部系统你的压测请求发出去之后很大概率触发风控而且你无法控制外部网关的响应速度这会让压测结果掺杂大量不可控噪音。我个人的方案是分场景处理如果目标是评估自己系统的支付链路的处理能力订单状态流转、支付回调解析、库存确认就用Mock的方式在压测环境里用一个本地接口模拟支付网关的回调通知定时向支付回调接口推送成功/失败消息。如果目标是验证支付网关集成的正确性比如签名验签、回调参数解析就走沙箱环境用少量并发做功能验证不做性能压测。在JMeter里Mock支付回调有个小技巧压测线程发起支付请求后用一个固定定时器延迟几百毫秒再用另一个HTTP请求模拟支付网关向回调接口发送通知。这样能在一条压测脚本里完整覆盖用户支付→支付回调→订单状态更新的闭环。注意回调接口的验签逻辑如果系统会验证签名Mock也要把签名逻辑实现完整否则回调请求会被拒之门外链路根本走不通。4.3 断言与结果校验压测请求成功不等于业务成功新手最容易忽略的是HTTP状态码200不代表业务成功。购物系统里尤其如此——下单接口可能返回200表示请求接收成功但业务响应体里的code可能是50003含义是库存不足。所以在脚本里必须对每个关键请求添加业务级断言浏览接口断言返回商品列表非空商品ID格式正确。加购接口断言返回购物车条目ID商品数量为预期值。下单接口断言订单号已生成且订单状态为待支付。支付接口断言支付状态为已受理或支付成功。回调接口断言订单状态已流转为已支付。如果断言失败率在压测结果里偏高说明问题不一定是性能瓶颈很可能是业务逻辑本身出现了并发缺陷。这类断言能帮你把性能问题和逻辑问题在压测结束前就区分开。5. 加压策略与容量探测找到系统的真实拐点5.1 阶梯加压从低到高逐步逼近极限容量探测最常用的方法是阶梯加压。我常用的参数设置如下初始并发数设为预估容量的10%。每3~5分钟升一档每档增幅为当前并发数的30%~50%。每档运行期间稳定施压不加新增压力观察各项指标是否趋于平稳。出现拐点后再退回上一档验证系统能否在那个压力水平下稳定运行。阶梯加压最大的价值在于能找到系统的临界拐点。这个拐点不是一个精确数值而是一个范围比如4000并发时平均响应时间150ms5000并发时变成850ms那拐点大致就在4000到5000之间。这个区间就是你需要重点关注的容量边界。5.2 三个拐点优先看哪个压测过程中有三个信号可以帮你判断系统是否到了容量极限响应时间拐点平均RT或P95 RT突然大幅上升。这是最直观的信号。错误率拐点错误率从0开始明显爬升。这里要区分是超时错误、连接拒绝错误还是业务断言失败不同类型对应不同的根因方向。资源拐点CPU、内存、磁盘IO、网络带宽某个资源率先到达瓶颈。我的观察经验是先看资源拐点再看响应时间拐点最后核对错误率。因为资源到达瓶颈往往是最本质的原因响应时间恶化是表象错误率是最终结果。如果CPU只用了30%RT就开始涨优先怀疑锁竞争、Full GC频繁等应用层问题如果CPU已经打满90%以上直接考虑扩容或优化框架吞吐能力。5.3 稳定性压测要跑多久才够容量探测做完之后稳定性压测是另一码事。容量压测测的是上限稳定性压测测的是持续运行的能力。我一般要求稳定性压测至少持续2小时推荐4小时以上。核心观察点是内存使用曲线是否平滑有没有持续上升的迹象判断是否内存泄漏。GC频率和单次GC耗时是否稳定。数据库连接池、Redis连接池、HTTP连接池的使用量是否在一个稳定范围内波动。消息队列的消费积压是否有持续增长趋势。响应时间的P99是不是随着时间推移逐步劣化。最典型的稳定性问题就是内存泄漏压测跑到第30分钟内存上升到60%第60分钟75%第90分钟90%然后开始频繁Full GC吞吐量直线下滑。这类问题靠短时间压测根本发现不了必须跑足够长的时长。6. 监控与定位从网关到数据库的全链路指标串联6.1 分层监控每一层看什么指标压测过程中的监控直接决定了定位效率。我在压测期间至少会盯以下四层指标接入层网关或负载均衡的QPS、TPS、平均响应时间、错误状态码分布以及带宽流量。应用层每个微服务的线程池活跃线程数、队列积压量、方法级RT、GC频率和耗时、堆内存使用率。数据层数据库的活跃连接数、慢SQL数量、锁等待次数Redis的命中率、内存使用率、大Key热点Key访问情况消息队列的生产消费速率与积压数量。基础设施层容器CPU/内存使用率、磁盘IO吞吐和延迟、网络重传率。这里有一个很关键的技巧压测开始前必须先建立基准线。记录系统空闲状态下的各项指标作为对比基底压测过程中的数据才有参照意义。否则你看到一个慢SQL耗时800ms但不知道空闲时它是200ms还是400ms定位方向就会跑偏。6.2 一次典型的下单慢定位排查流程用一次真实的排查过程来说明监控数据怎么串联。压测跑起来之后发现下单接口的响应时间从500ms恶化到1500ms但订单服务CPU使用率只有40%。这时候怎么找根因我一般按这个顺序排查先看订单服务的线程池是否被打满。结果发现活跃线程数没满排除线程池瓶颈。看下单接口调用的下游耗时分布。发现订单服务调用库存服务的耗时高达900ms问题定位在库存服务。再钻到库存服务的监控数据里发现Redis的响应时间均值变成了12ms正常应该是0.5ms以内。进一步查Redis的慢日志发现有一个热点Key——爆款商品的库存Key每秒访问量超过10万次触发了Redis的单线程阻塞。到这里根因就清楚了压测流量高度集中在爆款商品上导致单个Redis Key成了热点把整个Redis实例的响应时间都拉高了。解决方案是给库存Key做分片或者引入本地缓存异步扣减。这套排查链路没有分层监控数据做支撑根本走不下去。压测设计方案里如果没包含监控方案测试进行到一半大概率会变成盲人摸象。6.3 压测期间最容易忽略的三个监控盲区有些指标平时不显眼但购物系统压测时它们往往会成为隐形瓶颈连接池耗尽应用连接池的最大连接数和压测需要的并发数是需要匹配的。如果压测并发超过了数据库连接池上限会出现大量连接等待超时。这类问题不会直接暴露在接口RT上但会在线程池活跃度上留下痕迹。日志写入阻塞压测期间日志量暴增磁盘IO被日志写入拖垮但业务代码本身没有问题。如果压测时发现业务进程CPU不高但RT很高可以先查一下日志落盘是否阻塞了主线程特别是同步日志框架容易出现这类问题。消息堆积下单成功后系统会发送各种MQ消息如果消费端处理能力跟不上消息堆积会越来越严重最终导致订单状态迟迟不流转。监控面板上要重点盯消费积压趋势而不是只看生产速率。7. 购物系统压测的经典雷区与解法7.1 并发场景下的库存超卖问题怎么压出来库存超卖是所有购物系统压测里最经典的问题。常规的压测如果只是把下单接口打一遍可能200并发都测不出超卖因为单机数据库的行锁已经把请求串行化了。要专门测超卖问题需要设计一个有针对性的事务并发场景把同一商品SKU的库存设置为一个很小的值比如30然后让大量虚拟用户同时对这一个SKU发起购买请求。观察最终的有效订单数量是否超过库存量。这个场景有几个地方要注意压测工具对同一个接口的并发请求如果做了请求合并或者缓存了响应会导致部分请求根本没到达服务器测不出真实并发。需要在脚本里对每个请求做随机参数化防止压测工具层面的优化。库存扣减的逻辑如果是先查库存→判断充足→扣除在高并发下必然会出现超卖因为这三步之间没有原子性保障。压测结果里如果出现超卖直接说明了业务代码存在并发缺陷需要改成原子化扣减操作或者加分布式锁。稳定性压测期间随机插入几次超卖专项测试可以验证线上实际运行的逻辑是否真的彻底解决了超卖问题而不只是单测里过了。7.2 支付回调重复通知与订单幂等支付系统的回调通知机制天然具备重试特性支付网关为了保证通知送达会按照一定的间隔反复推送回调通知直到业务方返回明确成功标识。压测时如果脚本模拟了回调流程大概率会遇到重复通知。这时候问题的关键是业务系统的幂等逻辑是否经得起并发验证。在压测脚本中专门设计一个场景对同一个订单同时发送5个内容一致的支付成功回调。观察系统是否出现了重复发货、重复入账、订单金额翻了5倍等问题。如果压测暴露了这类问题通常修复方向是回调处理逻辑里增加以订单号支付流水号为唯一键的幂等校验或者对订单的状态流转加分布式一致性保护。这类问题在功能测试里很难触发因为功能测试很少刻意构造并发重复请求但压测里极其容易出现。7.3 压测对被压系统与真实用户的影响控制最后聊一个非常现实的问题压测本身会对系统的稳定性产生冲击尤其当压测环境不独立、跟开发环境或者预发环境共用基础设施的时候压测流量有可能影响到其他团队的正常使用。我的做法是优先使用独立压测环境如果做不到至少把压测流量打到独立的服务实例上并明确服务注册发现的路由策略。压测前调整限流阈值。很多系统有自我保护机制不调整的话压测流量可能会触发限流导致结果全是429测了个寂寞。压测开始前发通知给相关团队确认不影响线上业务。压测结束后依次摘除压测流量、回滚配置、清理数据检查核心监控恢复正常。还有一个很多人忽略的细节压测结果必须保留原始日志和监控截图。这类数据是你的压测结论的依据也是后续优化后对比效果的参照。没有过程数据支撑的压测结论在评审会议上很容易被挑战有了原始素材才能让结论站得住脚。购物系统的压测设计说到底是要理解用户在这个系统里怎么走、系统内部怎么流转、资源在哪里被消耗然后把这种理解转成场景、数据和监控的可执行清单。没必要追求单接口的极限QPS那些数字看起来漂亮但真正决定购物系统线上表现的是整条链路在混合流量下的协同能力。先从业务目标倒推模型再按链路逐段压测和整体混压最后用分层监控定位瓶颈这套流程走下来你手里的压测报告才真正算数。
