跳至主要内容

Web 应用程序的负载测试策略:在用户之前找到突破点

使用 k6、Artillery 和 Locust 对 Web 应用程序进行负载测试。涵盖测试设计、流量建模、性能基线和结果解释策略。

E
ECOSIRE Research and Development Team
|2026年3月16日5 分钟阅读855 字数|

80% 的性能问题是由最终用户发现的,而不是通过测试发现的。 负载测试通过在用户遇到问题之前根据应用程序模拟现实世界的流量模式来翻转这一比例。处理黑色星期五流量的网站和崩溃的网站之间的区别几乎总是在于是否有人先运行负载测试。

属于我们的Performance & Scalability系列

阅读完整指南

80% 的性能问题是由最终用户发现的,而不是通过测试发现的。 负载测试通过在用户遇到问题之前根据应用程序模拟现实世界的流量模式来翻转这一比例。处理黑色星期五流量的网站和崩溃的网站之间的区别几乎总是在于是否有人先运行负载测试。

本指南涵盖了 Web 应用程序、电子商务平台和 ERP 系统的负载测试方法、工具选择、测试设计和结果解释。

要点

  • 负载测试应该模拟真实的用户行为,而不仅仅是锤击单个端点
  • 在优化之前建立性能基线——你无法改进你没有测量过的东西
  • 在类似生产的环境中运行负载测试;分期结果可能无法反映生产行为
  • 在 CI/CD 中自动进行负载测试,以在部署前捕获性能回归

负载测试的类型

测试类型目的持续时间负载模式
烟雾测试验证最小负载下的基本功能1-2 分钟1-5 个用户
负载测试验证预期流量下的性能10-30 分钟交通正常
压力测试找到突破点15-30 分钟逐渐增加
尖峰测试测试突发流量激增5-10 分钟突然跳跃
浸泡测试检测内存泄漏和退化2-8小时持续正常负载

使用 k6 进行负载测试

基本负载测试

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

export const options = {
  stages: [
    { duration: '2m', target: 50 },   // Ramp up to 50 users
    { duration: '5m', target: 50 },   // Stay at 50 users
    { duration: '2m', target: 100 },  // Ramp up to 100 users
    { duration: '5m', target: 100 },  // Stay at 100 users
    { duration: '2m', target: 0 },    // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    http_req_failed: ['rate<0.01'],
    http_reqs: ['rate>100'],
  },
};

export default function () {
  // Simulate realistic user behavior
  const homeResponse = http.get('https://example.com/');
  check(homeResponse, {
    'homepage status is 200': (r) => r.status === 200,
    'homepage loads in under 1s': (r) => r.timings.duration < 1000,
  });
  sleep(Math.random() * 3 + 1); // 1-4 seconds think time

  const productsResponse = http.get('https://example.com/api/v1/products');
  check(productsResponse, {
    'products API is 200': (r) => r.status === 200,
    'products API under 500ms': (r) => r.timings.duration < 500,
  });
  sleep(Math.random() * 2 + 1);
}

电子商务用户旅程测试

// k6/ecommerce-journey.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';

export const options = {
  scenarios: {
    browsing: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '5m', target: 200 },
        { duration: '10m', target: 200 },
        { duration: '5m', target: 0 },
      ],
      exec: 'browsingScenario',
    },
    purchasing: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '5m', target: 20 },
        { duration: '10m', target: 20 },
        { duration: '5m', target: 0 },
      ],
      exec: 'purchaseScenario',
    },
  },
  thresholds: {
    'http_req_duration{scenario:browsing}': ['p(95)<800'],
    'http_req_duration{scenario:purchasing}': ['p(95)<2000'],
    http_req_failed: ['rate<0.01'],
  },
};

export function browsingScenario() {
  group('Browse Products', () => {
    http.get('https://store.example.com/');
    sleep(2);
    http.get('https://store.example.com/products');
    sleep(3);
    http.get('https://store.example.com/products/sample-product');
    sleep(2);
  });
}

export function purchaseScenario() {
  group('Purchase Flow', () => {
    // Browse
    http.get('https://store.example.com/products/sample-product');
    sleep(1);

    // Add to cart
    http.post('https://store.example.com/api/cart', JSON.stringify({
      productId: 'prod_123',
      quantity: 1,
    }), { headers: { 'Content-Type': 'application/json' } });
    sleep(2);

    // Checkout
    http.get('https://store.example.com/cart');
    sleep(3);

    // Place order (simulated)
    const orderResponse = http.post('https://store.example.com/api/checkout/validate', JSON.stringify({
      email: `test-${__VU}@example.com`,
    }), { headers: { 'Content-Type': 'application/json' } });

    check(orderResponse, {
      'checkout validates': (r) => r.status === 200 || r.status === 201,
    });
    sleep(1);
  });
}

压力测试(找到突破点)

// k6/stress-test.js
import http from 'k6/http';
import { check } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 100 },
    { duration: '5m', target: 100 },
    { duration: '2m', target: 200 },
    { duration: '5m', target: 200 },
    { duration: '2m', target: 500 },
    { duration: '5m', target: 500 },
    { duration: '2m', target: 1000 },
    { duration: '5m', target: 1000 },
    { duration: '5m', target: 0 },
  ],
};

export default function () {
  const res = http.get('https://example.com/api/v1/products');
  check(res, {
    'status is 200': (r) => r.status === 200,
  });
}

解释结果

关键指标

公制健康警告关键
P95 响应时间<500 毫秒500ms-2s>2秒
P99 响应时间<1秒1-5秒>5秒
错误率<0.1%0.1-1%>1%
吞吐量达到目标目标的 80%<目标的 80%

常见瓶颈模式

CPU 限制瓶颈:响应时间随负载线性增加。 P95 和 P99 慢慢分开。

  • 修复:优化热代码路径、增加CPU容量或水平扩展

数据库瓶颈:响应时间在特定负载阈值下呈指数增长。连接池耗尽。

内存瓶颈:随着时间的推移逐渐退化。 GC 暂停会导致延迟峰值。

  • 修复:增加内存、修复内存泄漏、优化对象分配

网络瓶颈:所有端点的响应时间均匀增加。带宽饱和。

  • 修复:静态资产的 CDN、压缩、减少有效负载大小

性能基线

建立基线

在优化之前,记录您当前的性能:

# Run baseline test
k6 run --out json=baseline-results.json k6/load-test.js

# Compare after optimization
k6 run --out json=optimized-results.json k6/load-test.js

绩效预算

定义每个端点可接受的性能:

端点P95 目标吞吐量目标
主页500 毫秒200 请求/秒
产品列表800 毫秒150 请求/秒
产品详情600 毫秒200 请求/秒
添加到购物车300 毫秒100 请求/秒
结帐2000 毫秒50 请求/秒
搜索500 毫秒100 请求/秒
管理仪表板1500 毫秒20 请求/秒

CI/CD 集成

自动性能回归测试

# .github/workflows/performance.yml
name: Performance Test
on:
  push:
    branches: [main]

jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run k6 load test
        uses: grafana/k6-action@v0.3.1
        with:
          filename: k6/load-test.js
          flags: --out json=results.json
        env:
          K6_TARGET_URL: ${{ secrets.STAGING_URL }}

      - name: Check thresholds
        run: |
          if grep -q '"thresholds":{".*":"fail"' results.json; then
            echo "Performance thresholds exceeded!"
            exit 1
          fi

负载测试清单

测试前

  • 环境与生产环境匹配(实例类型、数据库大小)
  • 测试数据具有代表性(实际产品数量、用户数量)
  • 监控处于活动状态(在测试期间跟踪服务器指标)
  • 通知利益相关者(负载测试可以触发警报)
  • CDN 和缓存的配置与生产中相同

测试期间

  • Monitor server CPU, memory, disk I/O
  • 监控数据库连接和查询延迟
  • 注意错误率增加
  • 检查资源耗尽(文件描述符、连接)
  • 注意性能下降的负载级别

测试后

  • 记录基线结果
  • 识别瓶颈及其负载阈值
  • 创建票证以提高性能
  • 与绩效预算进行比较
  • 安排优化后的后续测试

常见问题

我们应该加载测试生产还是登台?

如果可能的话,两者都可以。用于定期测试和 CI/CD 集成的阶段。定期验证(在低流量时段)的生产,因为分段通常在数据库大小、缓存温度、CDN 配置和网络拓扑方面存在差异。如果您只能测试一种环境,请测试暂存环境,但尽可能使其与生产环境类似。

我们应该多久运行一次负载测试?

对每个部署进行冒烟测试(在 CI/CD 中自动化)。每周或在主要版本发布之前进行满负载测试。每季度或在已知的高流量事件(销售、发布)之前进行压力测试。每季度进行一次 Soak 测试,以检测内存泄漏和长期退化。

我们如何对 ERP 系统进行负载测试?

ERP 负载测试需要模拟并发用户执行不同的任务:生成发票、创建采购订单、运行报告、导入数据。重点关注最繁重的操作(报告生成、数据导入)和最并发的操作(高峰时段的订单输入)。 ECOSIRE 提供 Odoo 性能测试 作为我们支持服务的一部分。

请求之间的实际思考时间是多少?

对于电子商务浏览:2-5 秒。表格填写:10-30 秒。结账时间:15-60 秒。对于管理/ERP 使用:5-15 秒。始终在负载测试中添加随机思考时间——恒定的间隔会创建不切实际的同步负载模式。


接下来会发生什么

负载测试揭示了指导您优化工作的瓶颈。跟进针对数据库瓶颈的数据库扩展、针对静态资产交付的CDN优化以及针对弹性容量的自动缩放。

联系 ECOSIRE 以进行性能测试和优化,或探索我们的 DevOps 指南 以了解完整的基础架构策略。


由 ECOSIRE 发布——帮助企业构建在压力下运行的应用程序。

E

作者

ECOSIRE Team

Technical Writing

The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.

ECOSIRE

与 ECOSIRE 一起发展您的业务

跨 ERP、电子商务、人工智能、分析和自动化的企业解决方案。