انتقل إلى المحتوى الرئيسي

اختبار التحميل k6: اختبار الضغط على واجهات برمجة التطبيقات الخاصة بك قبل الإطلاق

اختبار التحميل الرئيسي لـ k6 لواجهات برمجة تطبيقات Node.js. يغطي عمليات تكثيف المستخدم الافتراضي، والعتبات، والسيناريوهات، وHTTP/2، واختبار WebSocket، ولوحات معلومات Grafana، وأنماط تكامل CI.

E
ECOSIRE Research and Development Team
|19 مارس 202611 دقائق قراءة2.1k كلمات|

يعد شحن منتج دون اختبار التحميل بمثابة مقامرة. أنت لا تعرف نقطة الانهيار الخاصة بك حتى يجدها المستخدمون لك - عادةً أثناء إطلاق المنتج، أو لحظة الانتشار، أو ارتفاع المبيعات. k6 هي أداة اختبار التحميل الحديثة التي تتيح لك كتابة الاختبارات في JavaScript، وتشغيلها من CI، واكتشاف سقف الأداء الخاص بك قبل أن يفعله المستخدمون.

جزء من سلسلة Performance & Scalability

اقرأ الدليل الكامل

يعد شحن منتج دون اختبار التحميل بمثابة مقامرة. أنت لا تعرف نقطة الانهيار الخاصة بك حتى يجدها المستخدمون لك - عادةً أثناء إطلاق المنتج، أو لحظة الانتشار، أو ارتفاع المبيعات. k6 هي أداة اختبار التحميل الحديثة التي تتيح لك كتابة الاختبارات في JavaScript، وتشغيلها من CI، واكتشاف سقف الأداء الخاص بك قبل أن يفعله المستخدمون. إنه صديق للمطورين، وفعال في استخدام الموارد (يستخدم k6 goroutines، وليس سلاسل الرسائل)، ويتكامل بشكل نظيف مع Grafana وPrometheus للحصول على مقاييس في الوقت الفعلي.

يغطي هذا الدليل k6 بدءًا من البرنامج النصي الأول وحتى اختبارات التحميل المعقدة متعددة السيناريوهات والمقاييس المخصصة والعتبات وتكامل CI وأنماط الاختبار الآمنة للإنتاج لواجهات برمجة تطبيقات Node.js/NestJS.

الوجبات الرئيسية

  • البرامج النصية k6 هي JavaScript ولكنها تعمل في وقت تشغيل Go - لا توجد واجهات برمجة تطبيقات Node.js (لا يوجد require، لا fs، لا setTimeout)
  • المستخدمون الظاهريون (VUs) هم مستخدمون متزامنون تمت محاكاتهم؛ التكرارات هي عمليات تنفيذ البرنامج النصي الفردية
  • قم دائمًا بتعيين الحدود قبل التشغيل - تؤدي الحدود الفاشلة إلى إيقاف الاختبار وفشل CI
  • استخدم السيناريوهات (الثابت، والتصاعدي، ومعدل الوصول الثابت) لنمذجة أنماط حركة المرور الحقيقية
  • لا تقم مطلقًا بتحميل اختبار الإنتاج دون التنسيق مع العمليات - قم بإجراء اختبار ضد التدريج أو استنساخ الإنتاج
  • مقياس http_req_duration هو مقياس SLA الأساسي الخاص بك - p95 وp99 مهمان أكثر من المتوسطات
  • الحد الأقصى للمعدل الخاص بالعدائين k6 - يجب ألا تستنفد حركة مرور اختبار التحميل ميزانية الحد الأقصى للمعدل الخاص بك
  • استخدم k6 Cloud أو Grafana k6 لاختبار الحمل الموزع عبر المناطق الجغرافية

التثبيت والسيناريو الأول

# macOS
brew install k6

# Ubuntu/Debian
sudo gpg -k && sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] 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

# Windows (via Chocolatey)
choco install k6

أول نص k6 الخاص بك:

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

// Custom metric: error rate
const errorRate = new Rate('error_rate');

export const options = {
  // Ramp up to 50 VUs over 1 minute, hold 2 minutes, ramp down
  stages: [
    { duration: '1m', target: 50 },
    { duration: '2m', target: 50 },
    { duration: '1m', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'], // 95th pct < 500ms
    http_req_failed:   ['rate<0.01'],                // Less than 1% errors
    error_rate:        ['rate<0.01'],
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';

export default function () {
  const res = http.get(`${BASE_URL}/`);

  const ok = check(res, {
    'status is 200':              (r) => r.status === 200,
    'response time < 500ms':     (r) => r.timings.duration < 500,
    'body contains ECOSIRE':     (r) => r.body.includes('ECOSIRE'),
  });

  errorRate.add(!ok);
  sleep(1); // 1 second think time between iterations
}

تشغيله:

k6 run k6/homepage-load.js
k6 run --env BASE_URL=https://staging.ecosire.com k6/homepage-load.js
k6 run --vus 100 --duration 30s k6/homepage-load.js  # Quick smoke test

اختبار تحميل واجهة برمجة التطبيقات مع المصادقة

// k6/api-load.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { Counter, Trend } from 'k6/metrics';

const apiCalls = new Counter('api_calls');
const authDuration = new Trend('auth_duration');

export const options = {
  scenarios: {
    // Constant arrival rate: 100 requests/second regardless of VU count
    constant_load: {
      executor: 'constant-arrival-rate',
      rate: 100,
      timeUnit: '1s',
      duration: '3m',
      preAllocatedVUs: 50,
      maxVUs: 200,
    },
  },
  thresholds: {
    http_req_duration:            ['p(95)<300', 'p(99)<500'],
    'http_req_duration{type:api}': ['p(95)<200'],
    http_req_failed:              ['rate<0.005'], // 0.5% error budget
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:3001';

// setup() runs once before VUs start
export function setup() {
  // Authenticate and return tokens for VUs to use
  const loginRes = http.post(`${BASE_URL}/auth/login`, JSON.stringify({
    email: __ENV.TEST_EMAIL,
    password: __ENV.TEST_PASSWORD,
  }), {
    headers: { 'Content-Type': 'application/json' },
  });

  check(loginRes, { 'login successful': (r) => r.status === 200 });

  const { accessToken } = loginRes.json();
  return { accessToken };
}

export default function (data) {
  const { accessToken } = data;
  const headers = {
    Authorization: `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  };

  group('Contacts API', () => {
    // List contacts
    const listRes = http.get(`${BASE_URL}/contacts?page=1&limit=20`, {
      headers,
      tags: { type: 'api', endpoint: 'contacts-list' },
    });

    check(listRes, {
      'list contacts: 200': (r) => r.status === 200,
      'list contacts: has items': (r) => r.json('data') !== null,
    });

    apiCalls.add(1);
    sleep(0.5);

    // Create a contact
    const createRes = http.post(`${BASE_URL}/contacts`, JSON.stringify({
      name: `Load Test User ${Date.now()}`,
      email: `load-test-${Date.now()}@example.com`,
    }), {
      headers,
      tags: { type: 'api', endpoint: 'contacts-create' },
    });

    check(createRes, {
      'create contact: 201': (r) => r.status === 201,
    });

    apiCalls.add(1);
  });

  sleep(1);
}

// teardown() runs once after all VUs finish
export function teardown(data) {
  // Clean up test data if needed
  console.log(`Test complete. Data: ${JSON.stringify(data)}`);
}

سيناريوهات VU المتزايدة

نموذج أنماط حركة المرور الحقيقية بمراحل وسيناريوهات متعددة:

// k6/stress-test.js
export const options = {
  scenarios: {
    // Scenario 1: Normal traffic baseline
    normal_traffic: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 20 },
        { duration: '5m', target: 20 },
      ],
      gracefulRampDown: '30s',
    },

    // Scenario 2: Sudden spike (marketing campaign goes live)
    traffic_spike: {
      executor: 'ramping-vus',
      startVUs: 0,
      startTime: '7m', // Starts after baseline is stable
      stages: [
        { duration: '30s', target: 200 }, // Rapid spike
        { duration: '2m',  target: 200 }, // Hold spike
        { duration: '30s', target: 20  }, // Fall back
      ],
    },

    // Scenario 3: Soak test for memory leaks
    soak_test: {
      executor: 'constant-vus',
      vus: 10,
      duration: '30m', // Run for 30 minutes
      startTime: '10m',
    },
  },
  thresholds: {
    'http_req_duration{scenario:normal_traffic}': ['p(95)<300'],
    'http_req_duration{scenario:traffic_spike}':  ['p(95)<800'],
    http_req_failed:                              ['rate<0.02'],
  },
};

اختبار الزحف إلى المدونة/خريطة الموقع

// k6/sitemap-crawl.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';

// Load URLs from sitemap (pre-fetched and saved as JSON)
const urls = new SharedArray('sitemap urls', function () {
  // In real usage, fetch sitemap XML and parse URLs beforehand
  // SharedArray is loaded once and shared across all VUs (memory efficient)
  return JSON.parse(open('./sitemap-urls.json'));
});

export const options = {
  vus: 20,
  duration: '5m',
  thresholds: {
    http_req_duration: ['p(95)<2000'], // Public pages can be slower
    http_req_failed:   ['rate<0.01'],
  },
};

export default function () {
  // Each VU picks a random URL from the sitemap
  const url = urls[Math.floor(Math.random() * urls.length)];

  const res = http.get(url, {
    headers: {
      // Identify as load tester in logs
      'User-Agent': 'k6-load-tester/1.0',
    },
    timeout: '10s',
  });

  check(res, {
    'status is 200':           (r) => r.status === 200,
    'no error page':           (r) => !r.body.includes('Error'),
    'has canonical tag':       (r) => r.body.includes('rel="canonical"'),
    'response time < 2000ms': (r) => r.timings.duration < 2000,
  });

  sleep(Math.random() * 2 + 1); // Random 1-3 second think time
}

المقاييس والعتبات المخصصة

// k6/checkout-load.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Counter, Rate, Trend, Gauge } from 'k6/metrics';

// Custom metrics for business-specific tracking
const checkoutSuccesses = new Counter('checkout_successes');
const checkoutFailures  = new Counter('checkout_failures');
const checkoutDuration  = new Trend('checkout_duration');
const activeCheckouts   = new Gauge('active_checkouts');

export const options = {
  stages: [
    { duration: '1m', target: 10 },
    { duration: '3m', target: 30 },
    { duration: '1m', target: 0 },
  ],
  thresholds: {
    checkout_duration:   ['p(95)<3000'],   // Checkout < 3s at p95
    checkout_failures:   ['count<10'],     // Max 10 absolute failures
    http_req_duration:   ['p(99)<2000'],
  },
};

export default function (data) {
  activeCheckouts.add(1);
  const start = Date.now();

  // Step 1: Add to cart
  const cartRes = http.post('/api/cart/add', JSON.stringify({
    productId: 'prod_odoo_customization',
    quantity: 1,
  }), { headers: { 'Content-Type': 'application/json' } });

  if (!check(cartRes, { 'add to cart: 200': (r) => r.status === 200 })) {
    checkoutFailures.add(1);
    activeCheckouts.add(-1);
    return;
  }

  sleep(2);

  // Step 2: Create checkout session
  const checkoutRes = http.post('/api/billing/checkout', JSON.stringify({
    cartId: cartRes.json('cartId'),
  }), { headers: { 'Content-Type': 'application/json' } });

  const success = check(checkoutRes, {
    'checkout session created': (r) => r.status === 200,
    'has session URL':          (r) => r.json('url') !== null,
  });

  if (success) {
    checkoutSuccesses.add(1);
    checkoutDuration.add(Date.now() - start);
  } else {
    checkoutFailures.add(1);
  }

  activeCheckouts.add(-1);
  sleep(1);
}

تكامل إخراج جرافانا

قم ببث مقاييس k6 إلى InfluxDB وتصورها في Grafana أثناء الاختبار:

# Run with InfluxDB output
k6 run --out influxdb=http://localhost:8086/k6 k6/api-load.js

# Or use the k6 Grafana integration (k6 v0.45+)
k6 run --out experimental-prometheus-rw k6/api-load.js
# docker-compose.monitoring.yml
services:
  influxdb:
    image: influxdb:2.7
    ports: ["8086:8086"]
    environment:
      INFLUXDB_DB: k6
      INFLUXDB_ADMIN_USER: admin
      INFLUXDB_ADMIN_PASSWORD: password

  grafana:
    image: grafana/grafana:latest
    ports: ["3030:3000"]
    environment:
      GF_AUTH_ANONYMOUS_ENABLED: "true"
    volumes:
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./grafana/datasources:/etc/grafana/provisioning/datasources

التكامل مع CI

# .github/workflows/ci.yml (load test section)
load-tests:
  runs-on: ubuntu-latest
  if: github.ref == 'refs/heads/main' # Only on main branch
  needs: [deploy-staging]
  steps:
    - uses: actions/checkout@v4

    - name: Install k6
      run: |
        sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
          --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
        echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] 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 smoke test (30 VUs, 60s)
      run: |
        k6 run --vus 30 --duration 60s \
          --env BASE_URL=${{ secrets.STAGING_URL }} \
          --env TEST_EMAIL=${{ secrets.TEST_EMAIL }} \
          --env TEST_PASSWORD=${{ secrets.TEST_PASSWORD }} \
          apps/api/k6/api-load.js

    - name: Upload k6 results
      uses: actions/upload-artifact@v4
      if: always()
      with:
        name: k6-results
        path: k6-results/

خطوط الأساس للأداء

إنشاء خطوط الأساس والتنبيه على الانحدارات:

نقطة النهايةص50ص95ص99خطأ في الميزانية
GET / (الصفحة الرئيسية)120 مللي ثانية400 مللي ثانية800 مللي ثانية0.1%
GET /api/contacts50 مللي ثانية150 مللي ثانية300 مللي ثانية0.5%
POST /api/contacts80 مللي ثانية200 مللي ثانية400 مللي ثانية0.5%
POST /auth/login200 مللي ثانية500 مللي ثانية1000 مللي ثانية1%
POST /billing/checkout500 مللي ثانية1500 مللي ثانية3000 مللي ثانية1%
GET /blog/[slug]100 مللي ثانية300 مللي ثانية600 مللي ثانية0.1%

الأسئلة المتداولة

ما الفرق بين وحدات VU والطلبات في الثانية؟

VUs (المستخدمون الظاهريون) هم مستخدمون متزامنون تمت محاكاتهم. تقوم كل VU بتشغيل وظيفتك الافتراضية في حلقة. تعتمد الطلبات في الثانية على مدى سرعة تشغيل كل تكرار. إذا استغرق كل تكرار VU ثانيتين (بما في ذلك وقت النوم) وكان لديك 100 وحدة VU، فستحصل على 50 RPS تقريبًا. استخدم المنفذ constant-arrival-rate عندما تحتاج إلى استهداف RPS محدد بغض النظر عن مدة التكرار.

هل يجب أن أقوم بتحميل الاختبار مقابل الإنتاج أو التدريج؟

تفضل دائما التدريج. تخاطر اختبارات حمل الإنتاج بالتأثير على المستخدمين الحقيقيين، واستنفاد حدود المعدلات، وإنشاء أوامر حقيقية من بيانات الاختبار، وإثارة أحداث webhook الحقيقية. إذا كان يجب عليك اختبار الإنتاج، فقم بذلك أثناء فترات حركة المرور المنخفضة، واستخدم رموز الدفع الاختبارية، ولديك خطة التراجع. تدعم k6 Cloud إنشاء التحميل الموزع جغرافيًا حتى تتمكن من اختبار أداء حافة CDN الخاصة بك دون لمس الأصل.

كيف أتعامل مع انتهاء صلاحية رمز المصادقة أثناء الاختبارات الطويلة؟

بالنسبة للاختبارات القصيرة (أقل من 15 دقيقة)، احصل على رمز مميز في setup() وقم بتمريره إلى جميع وحدات VU عبر data. بالنسبة للاختبارات الطويلة (أكثر من 15 دقيقة)، قم بتنفيذ تحديث الرمز المميز في البرنامج النصي للاختبار: تحقق من عمر الرمز المميز في الوظيفة الافتراضية وقم بالتحديث عندما يقترب من انتهاء الصلاحية. قم بتخزين الرمز المميز في متغير JavaScript محلي لكل VU.

ما الفرق بين stages و scenarios؟

stages هو اختصار لسيناريو ramping-vus واحد - وهو جيد لأنماط الرفع/الثبات/الانحدار البسيطة. يمنحك scenarios التحكم الكامل: أنماط حركة مرور متزامنة متعددة، ومنفذون مختلفون لكل سيناريو، وحدود لكل سيناريو، ووضع علامات على السيناريو في المقاييس. استخدم scenarios لإجراء اختبار واقعي متعدد الأنماط (خط الأساس + ارتفاع + نقع في وقت واحد).

كم عدد وحدات VU التي يمكن لعملية k6 واحدة التعامل معها؟

يمكن لعملية k6 واحدة على الأجهزة الحديثة أن تدعم ما بين 5000 إلى 10000 وحدة VU وتولد 50000 إلى 100000 دورة في الثانية، اعتمادًا على مدى تعقيد البرنامج النصي وحجم الاستجابة. بالنسبة للأحمال الأعلى، استخدم k6 cloud أو قم بتشغيل مثيلات k6 متعددة خلف موزع التحميل. كل وحدة VU خفيفة الوزن للغاية (goroutine) - يستخدم k6 موارد أقل بكثير من JMeter أو Gatling لأعداد VU المكافئة.


الخطوات التالية

يكشف اختبار التحميل عن ملف الأداء الحقيقي لتطبيقك قبل أن يكتشفه المستخدمون تحت الضغط. تمنحك الأنماط الموجودة في هذا الدليل - سيناريوهات التعزيز والعتبات المخصصة وتكامل CI ولوحات معلومات Grafana - البنية الأساسية للعثور على اختناقات الأداء وإصلاحها بشكل مستمر.

تقوم ECOSIRE بإنشاء واجهات برمجة التطبيقات NestJS التي تم التحقق من صحتها من خلال اختبارات تحميل k6 التي تغطي الصفحة الرئيسية ونقاط نهاية واجهة برمجة التطبيقات وتدفقات الخروج وعمليات الزحف الكاملة لخريطة الموقع. استكشف خدماتنا الهندسية الخلفية للتعرف على كيفية بناء الأداء من اليوم الأول.

مشاركة:
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) والتجارة الإلكترونية والذكاء الاصطناعي والتحليلات والأتمتة.

المزيد من Performance & Scalability

Shopify تحسين السرعة: قائمة مراجعة فنية تحرك فعليًا العناصر الحيوية للويب الأساسية (2026)

قائمة التحقق من سرعة Shopify التي تم اختبارها ميدانيًا لعام 2026 - ما الذي يعمل بالفعل على تحسين LCP وINP وCLS في المتاجر الحقيقية، وما الذي يضيع الوقت، وكيفية تدقيق التطبيقات والموضوعات.

القائمة المرجعية للتدقيق الفني لتحسين محركات البحث لعام 2026: 47 عملية فحص نجريها على كل موقع عميل

قائمة مراجعة التدقيق الفني لتحسين محركات البحث المكونة من 47 نقطة والتي نقوم بتشغيلها على كل موقع عميل في عام 2026 - إمكانية الزحف والفهرسة والقواعد الأساسية وhreflang وCore Web Vitals والسجلات.

Odoo 19 HR: مصفوفة المهارات، الخطط المهنية، دورات الأداء

ترقية الموارد البشرية في Odoo 19: مصفوفة المهارات الأصلية، وتخطيط المسار الوظيفي، ودورات مراجعة الأداء، وشبكة مكونة من 9 صناديق، وتخطيط التعاقب، وتكامل نظام معلومات الموارد البشرية.

معايير أداء Odoo 19: أرقام ضبط PostgreSQL 17

معايير أداء Odoo 19 الواقعية: سرعة عميل الويب، وإنتاجية ORM، وإعدادات ضبط PG17، وتجميع الاتصالات، وأعداد العاملين، وحدود القياس.

تحسين تكلفة OpenClaw وكفاءة الرمز المميز على نطاق واسع

تحسين تكلفة الرمز المميز لـ OpenClaw: التخزين المؤقت السريع، وتوجيه النموذج، والتخزين المؤقت للاستجابة، وواجهات برمجة التطبيقات المجمعة، وحواجز حماية التكلفة لكل مستأجر لوكلاء الإنتاج.

التحديث التزايدي لـ Power BI للجداول التي يزيد عددها عن 10 ملايين صف

دليل التشغيل للتحديث التزايدي لـ Power BI لجداول صفوف تزيد عن 10 ملايين: تصميم الأقسام، وRangeStart/RangeEnd، وسياسات التحديث، وطي الاستعلام، وDirectQuery الهجينة.