Node.js: النشر والتحسين

آخر تحديث: 2026-08-26

بعد أن قام تشارلي بنشر واجهة برمجة التطبيقات (API) على خادم الإنتاج باستخدام node app.js، كانت العملية تتعطل بشكل متكرر بسبب استثناءات لم يتم التقاطها، مما تسبب في انقطاعات طويلة في الخدمة أثناء التشغيل غير المراقب. فقام بتطبيق PM2 لتمكين إعادة التشغيل التلقائي ووضع الكلستر، إلى جانب استخدام حاويات Docker ووكيل عكسي Nginx، مما أدى إلى رفع معدل توفر الخدمة من 95% إلى 99.9% — وأصبح أخيرًا قادرًا على النوم بهدوء.

ستتعلم:


1. إدارة العمليات PM2

(1) لماذا نحتاج إلى مدير عمليات؟

في بيئة الإنتاج، قد تتعطل عمليات Node.js بسبب استثناءات لم يتم التقاطها، أو تسربات في الذاكرة، أو عدم كفاية موارد النظام. إذا تم تشغيل العملية مباشرةً باستخدام node app.js، فلن يتم إعادة تشغيلها بعد التعطل، مما يؤدي إلى انقطاع الخدمة. يُعد PM2 أكثر برامج إدارة العمليات شيوعًا لـ Node.js، حيث يوفر ميزات إعادة التشغيل التلقائي، وإدارة السجلات، وموازنة الحمل، والمراقبة.

▶ مثال:(2) التثبيت والأوامر الأساسية

BASH
npm install -g pm2
BASH
pm2 start app.js --name "my-api"
pm2 restart my-api
pm2 stop my-api
pm2 حذف my-api
pm2 logs my-api
pm2 monit

(3) مرجع سريع لأوامر PM2 الشائعة

الأمر الوظيفة حالات الاستخدام الشائعة
pm2 start app.js تشغيل التطبيق النشر الأول
pm2 restart <name> إعادة تشغيل التطبيق بعد تحديث الكود
pm2 reload <name> إعادة التشغيل دون توقف تحديث بيئة الإنتاج
pm2 stop <name> الخدمة معلقة أثناء الصيانة
pm2 delete <name> حذف العملية إزالة تامة
pm2 logs [name] عرض السجل استكشاف الأخطاء وإصلاحها
pm2 monit لوحة التحكم للمراقبة في الوقت الفعلي مراقبة استخدام الموارد
pm2 list قائمة العمليات عرض حالة التشغيل
pm2 describe <name> تفاصيل العملية تشخيص متعمق
pm2 save حفظ قائمة العمليات إعدادات بدء التشغيل
pm2 startup إنشاء برنامج نصي لبدء التشغيل الاستعادة تلقائيًا بعد إعادة تشغيل الخادم

▶ مثال: بدء تشغيل PM2 ومراقبته

BASH
pm2 start app.js --name "charlie-api" -i max
pm2 monit
pm2 save
pm2 startup
TEXT 📖 للعرض فقط
┌─────┬──────────────┬─────────────┬─────────┬─────────┬──────────┐
│ id  │ name         │ mode        │ ↺       │ status  │ cpu      │
├─────┼──────────────┼─────────────┼─────────┼─────────┼──────────┤
│ 0   │ charlie-api  │ cluster     │ 15      │ online  │ 12%      │
│ 1   │ charlie-api  │ cluster     │ 2       │ online  │ 8%       │
│ 2   │ charlie-api  │ cluster     │ 0       │ online  │ 5%       │
│ 3   │ charlie-api  │ cluster     │ 1       │ online  │ 3%       │
└─────┴──────────────┴─────────────┴─────────┴─────────┴──────────┘


2. أساسيات دوكر

(1) لماذا نستخدم دوكر؟

يقوم Docker بتجميع التطبيقات وتبعياتها في صور حاويات، مما يضمن الاتساق عبر بيئات التطوير والاختبار والإنتاج. وقد أظهر كود تشارلي، الذي كان قد عمل بنجاح في السابق على جهازه المحلي، أخطاءً بعد نشره على الخادم بسبب الاختلافات في إصدارات Node.js، لكن Docker نجح في حل هذه المشكلة تمامًا.

▶ مثال:(2) كتابة ملف Dockerfile

DOCKERFILE
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s \
  CMD node healthcheck.js

USER node

CMD ["node", "app.js"]

▶ مثال:(3) تنسيق الخدمات المتعددة باستخدام Docker Compose

YAML
version: "3.8"

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - MONGO_URI=mongodb://mongo:27017/myapp
      - REDIS_URL=redis://redis:6379
    depends_on:
      - mongo
      - redis
    restart: always
    healthcheck:
      test: ["CMD", "node", "healthcheck.js"]
      interval: 30s
      timeout: 3s
      retries: 3

  mongo:
    image: mongo:7
    volumes:
      - mongo-data:/data/db
    restart: always

  redis:
    image: redis:7-alpine
    restart: always

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - api
    restart: always

volumes:
  mongo-data:

(4) مقارنة بين طرق النشر

البعد النشر على الأجهزة المادية النشر باستخدام دوكر النشر عبر الخدمات السحابية
الاتساق البيئي ضعيف ممتاز ممتاز
سرعة النشر بطيئة سريعة الأسرع
تعقيد العمليات مرتفع متوسط منخفض
استخدام الموارد مرتفع متوسط حسب الطلب
مرونة التوسع ضعيفة جيدة ممتازة
التكلفة منخفضة متوسطة عالية
حالات الاستخدام المشاريع الصغيرة المشاريع المتوسطة والكبيرة المشاريع على مستوى المؤسسات
منحنى التعلم منخفض متوسط مرتفع

▶ مثال: إنشاء صورة Docker وتشغيلها

BASH
docker build -t charlie-api:1.0 .
docker run -d -p 3000:3000 --env-file .env charlie-api:1.0
docker-compose up -d
docker-compose logs -f api


3. إدارة متغيرات البيئة

(1) لماذا نحتاج إلى متغيرات البيئة؟

تختلف إعدادات التكوين، مثل عناوين قواعد البيانات وأرقام المنافذ وكلمات المرور، باختلاف البيئات (التطوير والاختبار والإنتاج). ويؤدي تضمين هذه الإعدادات في الكود بشكل ثابت إلى مخاطر أمنية ويجعل التبديل بين البيئات أمراً صعباً. وتعمل متغيرات البيئة على فصل التكوين عن الكود، تماشياً مع منهجية «التطبيق ذي العوامل الاثني عشر».

▶ مثال:(2) ملفات dotenv و.env

JAVASCRIPT
const dotenv = require('dotenv');

dotenv.config({ path: `.env.${process.env.NODE_ENV || 'development'}` });

const config = {
  port: parseInt(process.env.PORT, 10) || 3000,
  mongoUri: process.env.MONGO_URI,
  jwtSecret: process.env.JWT_SECRET,
  redisUrl: process.env.REDIS_URL,
  logLevel: process.env.LOG_LEVEL || 'info',
};

module.exports = config;
▶ جرّب الكود

▶ مثال:(3) استراتيجية تكوين بيئة الإنتاج

TEXT 📖 للعرض فقط
.env                # Default Configuration(Do not submit Git)
.env.development    # Development Environment
.env.test           # Test Environment
.env.production     # Production Environment(Through CI/CD Inject,Do not コミット to the code リポジトリ)

(4) مقارنة بين طرق إدارة المتغيرات البيئية

الطريقة الأمان المرونة التعاون بين أعضاء الفريق حالات الاستخدام
.env ملف متوسط عالي جيد مشاريع عامة
متغيرات بيئة النظام عالية منخفضة رديئة سهولة النشر
أسرار دوكر عالية متوسطة جيدة بيئة دوكر
أسرار K8s عالي عالي ممتاز Kubernetes
مركز تكوين المنصة السحابية مرتفع مرتفع ممتاز المشاريع السحابية الأصلية

▶ مثال: تحميل تكوينات متعددة البيئات

JAVASCRIPT
const path = require('path');
const dotenv = require('dotenv');

const env = process.env.NODE_ENV || 'development';

const envFile = path.resolve(process.cwd(), `.env.${env}`);
dotenv.config({ path: envFile });

if (env === 'development') {
  dotenv.config({ path: path.resolve(process.cwd(), '.env') });
}

const required = ['MONGO_URI', 'JWT_SECRET'];
const missing = required.filter(key => !process.env[key]);

if (missing.length) {
  throw new Error(`Missing required env vars: ${missing.join(', ')}`);
}

module.exports = {
  env,
  port: parseInt(process.env.PORT, 10) || 3000,
  mongoUri: process.env.MONGO_URI,
  jwtSecret: process.env.JWT_SECRET,
  redisUrl: process.env.REDIS_URL,
  logLevel: process.env.LOG_LEVEL || 'info',
};
▶ جرّب الكود

4. وضع المجموعات

(1) لماذا نحتاج إلى المجموعات؟

يعمل Node.js بنظام خيط واحد، مما يعني أنه لا يمكن تشغيل سوى مثيل واحد على كل نواة من نوى وحدة المعالجة المركزية (CPU). ويستخدم وضع الكلستر (Cluster mode) الوحدة النمطية cluster لإنشاء عمليات عاملة متعددة، مما يتيح الاستفادة الكاملة من وحدات المعالجة المركزية متعددة النوى لتحسين معدل الإنتاجية والتوافر بشكل ملحوظ.

(2) كيفية عمل وحدة «cluster»

تتولى العملية الرئيسية (master) مسؤولية الاستماع على المنفذ وتوزيع الطلبات، بينما تتولى العمليات العاملة (worker) معالجة منطق الأعمال الفعلي. وتتشارك عدة عمليات عاملة في نفس المنفذ لتحقيق موازنة الحمل.

▶ مثال:(3) إعداد المجموعة يدويًّا

JAVASCRIPT
const cluster = require('cluster');
const os = require('os');
const http = require('http');

if (cluster.isPrimary) {
  const numCPUs = os.cpus().length;
  console.log(`Master ${process.pid} is running`);
  console.log(`Forking ${numCPUs} workers...`);

  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died. Restarting...`);
    cluster.fork();
  });
} else {
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`Handled by worker ${process.pid}\n`);
  }).listen(3000);

  console.log(`Worker ${process.pid} started`);
}
▶ جرّب الكود

(4) وضع المجموعة PM2

يتميز PM2 بدعم مدمج للمجموعات يمكن تفعيله دون الحاجة إلى تعديل الكود:

BASH
pm2 start app.js -i max
pm2 start app.js -i 4

▶ مثال: مقارنة أداء أوضاع المجموعات

BASH
node single.js &
ab -n 10000 -c 100 http://localhost:3000/
TEXT 📖 للعرض فقط
Single process:
  Requests per second:    3254.21 [#/sec]

Cluster (4 workers):
  Requests per second:    11280.67 [#/sec]


5. تحليل الأداء

(1) التوقيت الأساسي باستخدام console.time

أبسط طريقة لقياس الأداء، وهي مثالية لتحديد العمليات البطيئة بسرعة:

JAVASCRIPT
app.get('/api/users', غير متزامن (req, res) => {
  console.time('フェッチ-users');
  const users = انتظار User.find().lean();
  console.timeEnd('フェッチ-users');
  res.json(users);
});

(2) أدوات قياس الأداء: القياس الدقيق

توفر الوحدة المدمجة perf_hooks في Node.js توقيتًا عالي الدقة:

JAVASCRIPT
const { performance, PerformanceObserver } = require('perf_hooks');

const obs = new PerformanceObserver((list) => {
  const entries = list.getEntries();
  entries.forEach((entry) => {
    console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms`);
  });
});
obs.observe({ type: 'measure', buffered: true });

function measureAsync(label, fn) {
  return async (...args) => {
    performance.mark(`${label}-start`);
    const result = await fn(...args);
    performance.mark(`${label}-end`);
    performance.measure(label, `${label}-start`, `${label}-end`);
    return result;
  };
}

const fastQuery = measureAsync('db-query', async () => {
  return await User.find().lean();
});

(3) التشخيص الاحترافي باستخدام Clinic.js

Clinic.js هي مجموعة أدوات تشخيص الأداء الرسمية التي توصي بها Node.js:

BASH
npm install -g clinic
clinic doctor -- node app.js
clinic flame -- node app.js
clinic bubbleprof -- node app.js

(4) مقارنة بين استراتيجيات تحسين الأداء

الاستراتيجية الأدوات/الأساليب السيناريوهات القابلة للتطبيق الصعوبة الفعالية
تحليل التوقيت console.time تحديد الموقع السريع منخفض متوسط
القياس الدقيق perf_hooks المسار الحرج متوسط مرتفع
تحليل وحدة المعالجة المركزية Clinic Flame عمليات تستهلك موارد وحدة المعالجة المركزية بكثافة متوسطة عالية
حلقة الأحداث طبيب العيادة حجب عمليات الإدخال/الإخراج متوسط مرتفع
تسرب الذاكرة heapdump / memwatch مشكلات الذاكرة مرتفع مرتفع
اختبار الحمل مدفع آلي / ab تخطيط السعة منخفض متوسط
مراقبة APM New Relic / Datadog المراقبة المستمرة متوسطة عالية

▶ مثال: استخدام المدفع الآلي في اختبار الحمل

BASH
npm install -g autocannon
autocannon -c 100 -d 10 http://localhost:3000/api/health
TEXT 📖 للعرض فقط
┌─────────┬──────┬──────┬───────┬──────┬───────┬───────┬───────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg   │ Stdev │ Max   │
├─────────┼──────┼──────┼───────┼──────┼───────┼───────┼───────┤
│ Latency │ 2 ms │ 4 ms │ 12 ms │ 18ms │ 5 ms  │ 3 ms  │ 45 ms │
└─────────┴──────┴──────┴───────┴──────┴───────┴───────┴───────┘

Requests/sec: 18523.6


6. نقاط نهاية فحص الحالة الصحية

(1) لماذا تُعد الفحوصات الطبية ضرورية؟

تحتاج أنظمة تنسيق الحاويات (Docker، K8s) وأجهزة موازنة الحمل إلى معرفة ما إذا كانت الخدمة تعمل بشكل طبيعي. وتوفر نقاط نهاية فحص الحالة واجهة موحدة للكشف عن حالة الخدمة، مما يتيح الكشف التلقائي عن الأعطال وتحويل حركة المرور.

▶ مثال:(2) الفحص الصحي الأساسي

JAVASCRIPT
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
▶ جرّب الكود

▶ مثال:(3) فحص صحي شامل

JAVASCRIPT
app.get('/health', غير متزامن (req, res) => {
  const checks = {
    server: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage(),
  };

  try {
    انتظار mongoose.connection.db.admin().ping();
    checks.dataベース = 'ok';
  } catch (err) {
    checks.dataベース = 'error';
  }

  try {
    انتظار redisClient.ping();
    checks.redis = 'ok';
  } catch (err) {
    checks.redis = 'error';
  }

  const isHealthy = checks.dataベース === 'ok' && checks.redis === 'ok';
  res.status(isHealthy ? 200 : 503).json(checks);
});
▶ جرّب الكود

(4) الفصل بين الجاهزية والحيوية

في بيئات الإنتاج، تنقسم فحوصات الحالة عادةً إلى نوعين: فحص الحيوية (ما إذا كانت العملية قيد التشغيل) وفحص الجاهزية (ما إذا كانت جاهزة لاستقبال حركة المرور):

JAVASCRIPT
app.get('/healthz', (req, res) => {
  res.json({ status: 'alive' });
});

app.get('/readyz', async (req, res) => {
  try {
    await mongoose.connection.db.admin().ping();
    await redisClient.ping();
    res.json({ status: 'ready' });
  } catch {
    res.status(503).json({ status: 'not ready' });
  }
});

▶ مثال: برنامج نصي لفحص حالة Docker

JAVASCRIPT
const http = require('http');

const options = {
  hostname: 'localhost',
  port: process.env.PORT || 3000,
  path: '/healthz',
  timeout: 2000,
};

const req = http.request(options, (res) => {
  if (res.statusCode === 200) {
    process.exit(0);
  } else {
    process.exit(1);
  }
});

req.on('error', () => process.exit(1));
req.on('timeout', () => { req.destroy(); process.exit(1); });
req.end();
▶ جرّب الكود

7. الوكيل العكسي Nginx

(1) لماذا نحتاج إلى Nginx؟

يعمل Nginx كوكيل عكسي، حيث يوفر ميزات مثل إنهاء اتصال SSL، وموازنة الحمل، وتقديم الموارد الثابتة، والتحكم في معدل الطلبات. يركز Node.js على منطق الأعمال، بينما يتولى Nginx تحسين طبقة الشبكة؛ حيث يؤدي كل منهما دوره المحدد.

▶ مثال:(2) بنية نشر Node.js في بيئة الإنتاج

100%
graph LR
    Client[Client] --> Nginx[Nginx Reverse Proxy<br/>:80/:443]
    Nginx --> PM2[PM2 Cluster Management]
    PM2 --> W1[Worker 1<br/>:3000]
    PM2 --> W2[Worker 2<br/>:3000]
    PM2 --> W3[Worker 3<br/>:3000]
    PM2 --> W4[Worker 4<br/>:3000]
    W1 --> DB[(MongoDB)]
    W2 --> DB
    W3 --> Redis[(Redis)]
    W4 --> Redis

(3) تهيئة Nginx

TEXT 📖 للعرض فقط
upstream nodejs_backend {
    least_conn;
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    keepalive 64;
}

server {
    listen 80;
    server_name api.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate     /etc/ssl/certs/api.example.com.crt;
    ssl_certificate_key /etc/ssl/certs/api.example.com.key;

    location / {
        proxy_pass http://nodejs_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }

    location /health {
        proxy_pass http://nodejs_backend/health;
        access_log off;
    }

    location /static/ {
        alias /app/public/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
    limit_req zone=api burst=50 nodelay;
}

▶ مثال: تكوين الوكيل العكسي لـ Nginx في Docker

YAML
# docker-compose.yml Excerpt
nginx:
  image: nginx:alpine
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - ./nginx.conf:/etc/nginx/nginx.conf:ro
    - ./ssl:/etc/ssl/certs:ro
  depends_on:
    api:
      condition: service_healthy
  restart: always


8. مثال شامل: تكوين النشر الكامل

قام تشارلي بدمج جميع تكوينات النشر في نظام واحد، مما أدى إلى أتمتة العملية برمتها بدءًا من الكود وصولاً إلى مرحلة الإنتاج.

▶ مثال: هيكل مجلدات المشروع

TEXT 📖 للعرض فقط
charlie-api/
├── app.js
├── healthcheck.js
├── config/
│   └── index.js
├── ecosystem.config.js
├── Dockerfile
├── docker-compose.yml
├── nginx.conf
├── .env.example
├── .dockerignore
└── package.json

▶ مثال: ملف ecosystem.config.js في PM2

JAVASCRIPT 📖 للعرض فقط
module.exports = {
  apps: [
    {
      name: 'charlie-api',
      script: 'app.js',
      instances: 'max',
      exec_mode: 'cluster',
      autorestart: true,
      watch: false,
      max_memory_restart: '512M',
      env_development: {
        NODE_ENV: 'development',
        PORT: 3000,
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      error_file: './logs/error.log',
      out_file: './logs/out.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss',
      max_restarts: 10,
      restart_delay: 4000,
      kill_timeout: 5000,
      listen_timeout: 10000,
    },
  ],
  deploy: {
    production: {
      user: 'deploy',
      host: 'api.example.com',
      ref: 'origin/main',
      repo: 'git@github.com:charlie/api.git',
      path: '/var/www/charlie-api',
      'pre-deploy-local': '',
      'post-deploy':
        'npm ci && pm2 reload ecosystem.config.js --env production',
      'pre-setup': '',
    },
  },
};
42 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

▶ مثال: ملف Dockerfile (مُحسَّن لعمليات البناء متعددة المراحل)

DOCKERFILE
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build 2>/dev/null || true

FROM node:20-alpine AS production

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY --from=builder /app ./

RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
USER nodejs

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD node healthcheck.js

CMD ["node", "app.js"]

▶ مثال: .env.example

TEXT 📖 للعرض فقط
NODE_ENV=production
PORT=3000
MONGO_URI=mongodb://mongo:27017/charlie-api
REDIS_URL=redis://redis:6379
JWT_SECRET=your-secret-key-here
LOG_LEVEL=info
CORS_ORIGIN=https://example.com
RATE_LIMIT_WINDOW=60000
RATE_LIMIT_MAX=100

▶ مثال: healthcheck.js

JAVASCRIPT
const http = require('http');

const req = http.request(
  {
    hostname: '127.0.0.1',
    port: parseInt(process.env.PORT, 10) || 3000,
    path: '/healthz',
    timeout: 2000,
  },
  (res) => {
    process.exit(res.statusCode === 200 ? 0 : 1);
  }
);

req.on('error', () => process.exit(1));
req.on('timeout', () => { req.destroy(); process.exit(1); });
req.end();
▶ جرّب الكود

▶ مثال: نقطة نهاية فحص الحالة في ملف app.js

JAVASCRIPT 📖 للعرض فقط
const express = require('express');
const mongoose = require('mongoose');
const config = require('./config');

const app = express();
app.use(express.json());

app.get('/healthz', (req, res) => {
  res.json({ status: 'alive', pid: process.pid });
});

app.get('/readyz', async (req, res) => {
  try {
    await mongoose.connection.db.admin().ping();
    res.json({ status: 'ready', pid: process.pid, uptime: process.uptime() });
  } catch {
    res.status(503).json({ status: 'not ready', pid: process.pid });
  }
});

app.get('/health', async (req, res) => {
  const checks = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    pid: process.pid,
  };

  try {
    await mongoose.connection.db.admin().ping();
    checks.database = 'ok';
  } catch {
    checks.database = 'error';
    checks.status = 'degraded';
  }

  const isHealthy = checks.database === 'ok';
  res.status(isHealthy ? 200 : 503).json(checks);
});

app.get('/api/users', async (req, res) => {
  console.time('fetch-users');
  const users = await mongoose.model('User').find().lean();
  console.timeEnd('fetch-users');
  res.json({ success: true, data: users });
});

mongoose.connect(config.mongoUri).then(() => {
  app.listen(config.port, () => {
    console.log(`Server running on port ${config.port} [${config.env}]`);
  });
});

process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down gracefully...');
  mongoose.connection.close();
  process.exit(0);
});

process.on('SIGINT', () => {
  console.log('SIGINT received, shutting down gracefully...');
  mongoose.connection.close();
  process.exit(0);
});
55 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

❓ أسئلة شائعة

س هل يمكن استخدام Docker و PM2 معًا؟
ج نعم، ولكن عادةً ما يتم اختيار أحدهما دون الآخر. يعمل Node.js مباشرةً داخل حاوية Docker، حيث تتولى K8s أو Docker Compose إدارة عمليات إعادة التشغيل؛ أما PM2 فهو مناسب لعمليات النشر غير المُحاوية.
س كيف أختار منصة خدمات سحابية؟
ج بالنسبة للمشاريع الصغيرة، استخدم Vercel أو Railway (لا تتطلب أي إعدادات)؛ وبالنسبة للمشاريع المتوسطة الحجم، استخدم AWS أو GCP (مرنة لكنها معقدة)؛ أما بالنسبة للمشاريع في الصين، فاستخدم Alibaba Cloud أو Tencent Cloud. اختر بناءً على ميزانيتك وخبرة فريقك.
س ما هي بعض أدوات CI/CD الشائعة الاستخدام؟
ج GitHub Actions (تتكامل جيدًا مع GitHub)، وGitLab CI (مناسبة للاستضافة الذاتية)، وJenkins (مخصصة للمؤسسات، مع العديد من المكونات الإضافية)، وCircleCI (مصممة خصيصًا للسحابة).
س كيف يمكنني مراقبة تطبيق Node.js في بيئة الإنتاج؟
ج استخدم PM2 أو New Relic أو Datadog أو إعداد Prometheus + Grafana المُستضاف ذاتيًا لمراقبة وحدة المعالجة المركزية (CPU) والذاكرة ووقت الاستجابة ومعدلات الأخطاء.
س كيف يمكنني إجراء فحص الحالة بعد النشر؟
ج أضف نقطة النهاية /health، التي تُرجع { status: 'ok' }؛ حيث يقوم كل من Docker HEALTHCHECK أو K8s livenessProbe بالوصول إلى هذه النقطة بشكل دوري.

س: أيهما يجب أن أستخدم، PM2 أم Docker؟ ج: لا يستبعد أحدهما الآخر. يعمل Docker على معالجة مشكلات اتساق البيئة، بينما يتولى PM2 إدارة العمليات والتجميع. بالنسبة لبيئات الإنتاج، نوصي باستخدام Docker وPM2 معًا، بحيث يعمل PM2 داخل حاوية Docker لإدارة عمليات متعددة.

س: كيف يمكنني تحقيق نشر بدون توقف؟ ج: استبدل pm2 restart بـ pm2 reload. سيقوم PM2 بإعادة تشغيل العمال (Workers) واحدًا تلو الآخر، مما يضمن بقاء بعض المثيلات متصلة بالشبكة لمعالجة الطلبات في جميع الأوقات. في بيئة Docker، يمكنك استخدام استراتيجيات النشر الأزرق-الأخضر أو التحديث التدريجي.

س: ما هي قيود وضع الكلستر؟ ج: في وضع الكلستر، لا تتشارك العمليات الذاكرة؛ ويجب مشاركة الجلسات باستخدام وسائط تخزين خارجية مثل Redis؛ ويجب استخدام WebSockets بالاقتران مع الجلسات الثابتة أو مزامنة رسائل Pub/Sub؛ كما أن ذاكرة التخزين المؤقتة لنظام الملفات مستقلة لكل عملية على حدة.

س: كيف يمكنني مراقبة بيئة الإنتاج؟ ج: يوفر PM2 pm2 monit مراقبة في الوقت الفعلي وpm2 plus لوحة تحكم عبر الإنترنت؛ وتشمل الحلول التجارية New Relic وDatadog وPrometheus + Grafana؛ وتشمل المقاييس الرئيسية وحدة المعالجة المركزية (CPU) والذاكرة وزمن انتقال حلقة الأحداث ووقت استجابة الطلبات.

س: كيف يمكنني تقليل حجم صورة Docker؟ ج: استخدم الصورة الأساسية node:alpine، واستخدم عمليات البناء متعددة المراحل لفصل تبعيات التجميع، .dockerignore واستبعد الملفات غير الضرورية، npm ci --only=production وقم بتثبيت تبعيات الإنتاج فقط، وقم بمسح ذاكرة التخزين المؤقت لـ npm npm cache clean --force.

س: ما هي مزايا استخدام Nginx كوكيل عكسي؟ ج: يقلل تفريغ SSL من العبء الناتج عن التشفير على Node.js؛ ويتولى Nginx معالجة الموارد الثابتة مباشرةً؛ ويقوم موازنة الحمل بتوزيع الطلبات؛ ويمنع تحديد معدل الطلبات هجمات DDoS؛ ويقلل ضغط gzip من حجم نقل البيانات؛ كما يعمل التخزين المؤقت على تسريع الاستجابات.


📖 ملخص

(1) النقاط الرئيسية في هذا الدرس

(2) الواجبات المنزلية

  1. ابدأ مشروع Express الخاص بك باستخدام PM2، وقم بتكوين ecosystem.config.js، وقم بتمكين وضع الكلستر، واستخدم pm2 monit لمراقبة حالة التشغيل.
  2. اكتب ملف Dockerfile للمشروع، واستخدم عمليات البناء متعددة المراحل لتحسين حجم الصورة، واستخدم docker-compose لتشغيل Node.js وMongoDB في آن واحد
  3. قم بتنفيذ نقاط النهاية الثلاث الخاصة بفحص الحالة /health و/healthz و/readyz، وقم بتكوين HEALTHCHECK في Docker.
  4. استخدم autocannon لإجراء اختبار الحمل على واجهة برمجة التطبيقات (API) الخاصة بك، ومقارنة الفروق في معدل النقل بين وضع العملية الفردية ووضع المجموعة.
  5. اكتب إعدادات الوكيل العكسي لـ Nginx لتنفيذ إنهاء SSL وموازنة الحمل، واستخدم ab أو curl للتحقق من أن إعادة توجيه الوكيل تعمل بشكل صحيح.

📝 تمارين

  1. أكمل جميع أمثلة الأكواد الواردة في هذا الدرس وتأكد من أن كل منها يعمل بشكل صحيح.
  2. قم بتعديل المثال الشامل وأضف الإضافات الخاصة بك
  3. راجع الوثائق الرسمية، وحدد واجهة برمجة تطبيقات (API) واحدة أو اثنتين لم يتم تناولهما في هذا الدرس، واكتب كود اختبار لهما.
  4. التأمل: كيف ستطبق ما تعلمته في هذا الدرس على مشروع في الواقع العملي؟
  5. حاول أن تجمع بين ما تعلمته في هذا الدرس والمواد التي درستها في الدروس السابقة لإنشاء مشروع صغير.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%