mcpField
L
load-testing-commerce

load-testing-commerce

github.com/finsilabs测试与质量6075 次安装5 天前更新
代码仓库

关于 load-testing-commerce

面向开发者和电商团队的实用技能,帮助为店铺流量高峰做准备。内容涵盖使用 k6 和 Artillery 模拟购物流程、平台专项检查及结果分析。

主要功能

  1. 01模拟真实购物流程
  2. 02k6 与 Artillery 使用指导
  3. 03按平台制定测试方法
  4. 04性能基线与结果分析
  5. 05测试环境及数据安全实践

适用场景

  1. 01为季节性促销准备店铺
  2. 02发布前检查结账性能
  3. 03在预发布环境复现负载故障

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill load-testing-commerce

安装后在 Claude Code 中输入/load-testing-commerce确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/artillery-api-load-test/
criteria.json
task.md
evals/baseline-tracking-and-regression-detecti/
criteria.json
task.md
evals/k6-realistic-checkout-scenario/
criteria.json
task.md

详细介绍

负载测试 — Commerce

概述

对电商应用进行负载测试,不只是用并发请求猛击某个端点。逼真的测试场景会模拟实际用户行为:浏览商品目录、搜索商品、将商品加入购物车并完成结账,同时包含各个操作之间的思考时间。本技能涵盖如何在 k6 和 Artillery 中构建逼真的购物场景、解读结果以找到瓶颈,以及在大型促销活动前建立性能基线。

何时使用此技能

  • 准备限时抢购、黑色星期五或季节性流量高峰时
  • 部署重大基础设施变更(新数据库、CDN、结账服务)时
  • 为新网店建立性能 SLO 和基线时
  • 生产事故由负载导致,且需要在预发布环境中重现时
  • 即将发布新的结账功能,但其性能影响尚不明确时

核心说明

第 1 步:确定你的平台以及可测试的内容
平台负载测试范围推荐方法
ShopifyShopify 的基础设施会自动扩展,因此无法对其施加有意义的过载使用 Google PageSpeed Insights 和 Lighthouse CI 测试主题的前端性能;单独测试你托管的自定义应用或店面
WooCommerce服务器由你负责,因此销售活动前进行负载测试至关重要使用 Loader.io(免费层级:1 个目标,每次测试 10K 个连接)测试预发布网站;或使用 k6/Artillery 进行详细的场景测试
BigCommerceBigCommerce 会自动扩展,平台基础设施不是需要担心的问题使用 PageSpeed Insights 测试主题的前端性能;测试你托管的任何自定义中间件或无头层
自定义 / 无头可完全掌控,负载测试由你负责使用 k6(开源、可编写脚本)或 Artillery(基于 YAML),针对预发布环境运行逼真的购物场景
第 2 步:针对平台进行负载测试

Shopify

Shopify 的基础设施几乎可以应对任何流量高峰。负载测试应聚焦于前端体验:

  1. 使用 Google PageSpeed Insights 测试最关键的页面(商品页面、商品集合页面、结账页面):

    • 前往 pagespeed.web.dev,测试商品页面和商品集合页面
    • 移动端 Performance 分数目标为 75+;促销活动前处理所有红色或橙色建议
  2. 查看 Shopify 内置的性能报告:

    • 前往 Online Store → Themes,然后点击 View report
    • 该报告根据真实用户数据展示网店的 Core Web Vitals(LCP、CLS、FID)
    • 移动端 LCP 分数较差时,几乎总是意味着主视觉图片需要设置 fetchpriority="high" 或进行压缩
  3. 促销活动前检查已安装的应用:

    • 前往 Shopify 管理后台中的 Apps,检查所有会向店面注入脚本的应用
    • 每个第三方脚本都会增加 50–200ms;移除未使用的应用,并停用任何同步加载的应用
  4. 对于 Shopify Plus——重大上线前通知 Shopify 支持团队:

    • 至少在活动开始前 48 小时,通过 Plus 支持渠道提交限时抢购通知
    • Shopify 可以预先分配资源,并在活动期间监控你的网店

WooCommerce

WooCommerce 运行在你的托管基础设施上。请在与生产配置一致的预发布环境中进行测试(相同的 PHP 版本、MySQL 版本和缓存堆栈)。

使用 Loader.io 快速进行负载测试(无需编写代码):

  1. 在 loader.io 注册(免费套餐:1 个目标,每次测试最多 10K 个连接)
  2. 点击 New Test,输入你的预发布环境 URL
  3. 配置渐进式测试:从 0 个客户端开始,在 60 秒内逐步增加到预期峰值流量,并保持 120 秒
  4. Loader.io 会提供验证令牌——将其添加到预发布站点的某个页面,以确认所有权
  5. 专门针对结账页面运行测试——商品浏览通常会命中缓存;结账则会访问数据库

解读结果:

  • 峰值时响应时间低于 3 秒:可以接受
  • 错误率高于 1%:检查服务器日志,排查 PHP 致命错误、数据库超时或内存耗尽
  • 如果服务器在低于预期的流量下就吃力:请在促销前升级托管套餐或添加 Redis Object Cache

测试前,请确保预发布环境的堆栈已正确配置:

  • Redis Object Cache 已启用(Settings → Redis — 状态显示为绿色)
  • WP Rocket 页面缓存已启用,且访问量最高的商品页面已完成预热
  • 测试所用的服务器规格与生产环境相同

Custom / Headless

对于自定义店面,请使用 k6 或 Artillery,在与生产环境一致的预发布环境中进行测试。

开始前的重要事项:

  • 始终在预发布环境中测试,绝不在生产环境中测试
  • 使用测试模式支付令牌(Stripe:tok_visa)——绝不要针对真实支付处理服务运行负载测试
  • 使用容易识别的电子邮件域名标记测试订单(例如 @test-load.invalid),以便之后批量删除
  • 为测试商品预留无限库存,避免结账场景因超卖而失败

使用 k6 模拟真实购物场景:

k6 使用 ramping-arrival-rate 执行器控制每秒请求数(比基于 VU 的方法更贴近实际)。典型的电子商务流量分布:60% 浏览、25% 商品详情、10% 购物车、5% 结账。

// k6/commerce-load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';

const BASE_URL = __ENV.BASE_URL || 'https://staging.mystore.com';

const products = new SharedArray('products', function() {
  return JSON.parse(open('./data/products.json')); // array of {id, defaultVariantId}
});

export const options = {
  scenarios: {
    catalog_browsing: {
      executor: 'ramping-arrival-rate',
      startRate: 0, timeUnit: '1s',
      preAllocatedVUs: 50, maxVUs: 200,
      stages: [
        { target: 60, duration: '2m' },  // Ramp up
        { target: 60, duration: '5m' },  // Steady state
        { target: 0,  duration: '1m' },  // Ramp down
      ],
      exec: 'catalogBrowsing',
    },
    checkout_flow: {
      executor: 'ramping-arrival-rate',
      startRate: 0, timeUnit: '1s',
      preAllocatedVUs: 10, maxVUs: 50,
      stages: [
        { target: 5, duration: '2m' },
        { target: 5, duration: '5m' },
        { target: 0, duration: '1m' },
      ],
      exec: 'checkoutFlow',
    },
  },
  thresholds: {
    'http_req_duration{scenario:checkout_flow}': ['p(95)<3000'],
    'http_req_failed{scenario:checkout_flow}': ['rate<0.01'],
    'http_req_duration{scenario:catalog_browsing}': ['p(95)<1000'],
  },
};

export function catalogBrowsing() {
  http.get(`${BASE_URL}/api/collections/featured`, { tags: { step: 'homepage' } });
  sleep(2 + Math.random() * 3); // Think time: 2–5s

  const categories = ['t-shirts', 'hoodies', 'accessories'];
  const category = categories[Math.floor(Math.random() * categories.length)];
  http.get(`${BASE_URL}/api/collections/${category}?page=1&sort=popular`, { tags: { step: 'category' } });
  sleep(3 + Math.random() * 5);
}

export function checkoutFlow() {
  const product = products[Math.floor(Math.random() * products.length)];

  // View product
  const productRes = http.get(`${BASE_URL}/api/products/${product.id}`, { tags: { step: 'view_product' } });
  check(productRes, { 'product page 200': r => r.status === 200 });
  sleep(2 + Math.random() * 3);

  // Add to cart
  const cartRes = http.post(`${BASE_URL}/api/cart`, JSON.stringify({
    items: [{ productId: product.id, variantId: product.defaultVariantId, quantity: 1 }],
  }), { headers: { 'Content-Type': 'application/json' }, tags: { step: 'add_to_cart' } });
  check(cartRes, { 'add to cart 200': r => r.status === 200 });
  const cart = JSON.parse(cartRes.body);
  sleep(1 + Math.random() * 2);

  // Start checkout
  const checkoutRes = http.post(`${BASE_URL}/api/checkout/start`, JSON.stringify({
    cartId: cart.id,
    customer: { email: `test-${Math.random().toString(36).slice(7)}@test-load.invalid` },
  }), { headers: { 'Content-Type': 'application/json' }, tags: { step: 'start_checkout' } });
  check(checkoutRes, { 'checkout start 200': r => r.status === 200 });
  const checkout = JSON.parse(checkoutRes.body);
  sleep(5 + Math.random() * 10); // Think time: filling form

  // Place order
  const orderRes = http.post(`${BASE_URL}/api/checkout/complete`, JSON.stringify({
    checkoutId: checkout.id,
    paymentToken: 'tok_visa', // Stripe test token
    shippingMethodId: checkout.shippingMethods[0]?.id,
  }), { headers: { 'Content-Type': 'application/json' }, tags: { step: 'place_order' } });
  check(orderRes, { 'order placed 201': r => r.status === 201 });
}

Artillery YAML 配置(作为 k6 的替代方案,适合以 API 为重点的测试):

# artillery/commerce-load-test.yml
config:
  target: "{{ $processEnvironment.BASE_URL }}"
  phases:
    - name: "Warm-up"
      duration: 60
      arrivalRate: 5
    - name: "Ramp up"
      duration: 120
      arrivalRate: 5
      rampTo: 50
    - name: "Peak load"
      duration: 300
      arrivalRate: 50
    - name: "Spike"
      duration: 30
      arrivalRate: 200
    - name: "Recovery"
      duration: 60
      arrivalRate: 20
  processor: "./processors/commerce-helpers.js"

scenarios:
  - name: "Browse catalog"
    weight: 60
    flow:
      - get:
          url: "/api/collections/all?page=1"
          capture:
            json: "$.products[0].id"
            as: "productId"
      - think: 3
      - get:
          url: "/api/products/{{ productId }}"

  - name: "Complete checkout"
    weight: 10
    flow:
      - function: "generateCheckoutData"
      - post:
          url: "/api/cart"
          json:
            productId: "{{ productId }}"
            quantity: 1
          capture:
            json: "$.id"
            as: "cartId"
      - think: 8
      - post:
          url: "/api/checkout/start"
          json:
            cartId: "{{ cartId }}"
            email: "{{ email }}"
          capture:
            json: "$.checkoutId"
            as: "checkoutId"
      - think: 10
      - post:
          url: "/api/checkout/complete"
          json:
            checkoutId: "{{ checkoutId }}"
            paymentToken: "tok_visa"
          expect:
            - statusCode: 201

运行测试并采集基线数据:

k6 run \
  --env BASE_URL=https://staging.mystore.com \
  --out json=results/baseline-$(date +%Y%m%d).json \
  k6/commerce-load-test.js

使用标签按步骤分析结果:

# Overall summary (k6 outputs this automatically at end of run)
# Per-step breakdown from the JSON output
cat results/baseline-*.json | jq '
  [.data_points[] | select(.type=="Point" and .metric=="http_req_duration")]
  | group_by(.tags.step)
  | map({step: .[0].tags.step, p95_ms: (map(.value) | sort | .[length * 0.95 | floor])})
'

电子商务的扩展目标:

  • 目录页面 p95 < 1000ms
  • 商品详情 p95 < 1500ms
  • 结账流程 p95 < 3000ms
  • 峰值时错误率 < 1%

在重大版本发布前于 CI 中运行负载测试(GitHub Actions):

# .github/workflows/load-test.yml
name: Load Test (Pre-Release)
on:
  workflow_dispatch:
    inputs:
      target_url:
        description: 'Staging URL to test'
        required: true
        default: 'https://staging.mystore.com'

jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install k6
        run: |
          curl -s https://dl.k6.io/key.gpg | sudo apt-key add -
          echo "deb https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
          sudo apt-get update && sudo apt-get install k6
      - name: Run load test
        env:
          BASE_URL: ${{ github.event.inputs.target_url }}
        run: k6 run --env BASE_URL=$BASE_URL --out json=results.json k6/commerce-load-test.js
      - uses: actions/upload-artifact@v4
        with:
          name: load-test-results
          path: results.json

最佳实践

  • 模拟步骤之间的思考时间——真实用户会在操作之间停顿;去掉思考时间会产生不切实际的高请求率,无法反映生产环境中的使用模式
  • 使用测试模式支付令牌——始终使用 Stripe 的 tok_visa 或同等的测试令牌;绝不要针对真实支付处理服务运行负载测试
  • 在预发布环境而非生产环境中测试——负载测试会消耗资源,并可能影响真实客户的服务体验
  • 按照预期峰值的 1.5 倍、2 倍和 3 倍进行测试——在实际促销前,以不同负载级别运行多次测试,找出系统的拐点
  • 在测试期间监控数据库和缓存——留意数据库连接池耗尽、Redis 驱逐以及数据库中的锁等待时间;应用层吞吐量看起来可能正常,但数据库或许已经饱和
  • 为测试订单添加标记,以便轻松清理——使用统一的测试电子邮件域名(@test-load.invalid),以便在测试运行后批量删除测试数据:DELETE FROM orders WHERE email LIKE '%@test-load.invalid'

常见陷阱

问题解决方案
商品售罄,导致结账场景失败预留库存不限量的测试商品;使用单独的 is_load_test_product 标记,并在真实商品目录页面中将其过滤掉
Loader.io 测试通过,但生产环境仍然变慢预发布环境可能无法反映生产环境的负载情况——请确认 Redis Object Cache 已启用、MySQL 版本一致,并且服务器规格相同
k6 的 VU 耗尽,尚未达到目标 RPS使用 ramping-arrival-rate 执行器(控制 RPS),而不是 ramping-vus(控制并发用户数);增大 preAllocatedVUs 和 maxVUs
计划中的负载测试期间告警过多在监控系统中添加负载测试标记(Datadog 标签、Grafana 注释),以便在预定测试时段内抑制非关键告警
不同运行轮次的结果无法复现使用包含固定数据集文件的 SharedArray,而不是使用 Math.random() 生成商品;在运行前固定测试数据集

相关技能

  • @flash-sale-scaling
  • @monitoring-alerting-commerce
  • @database-optimization-commerce
  • @bot-protection

评论

支持 Markdown