Node.js: النشر والتحسين
آخر تحديث: 2026-08-26
بعد أن قام تشارلي بنشر واجهة برمجة التطبيقات (API) على خادم الإنتاج باستخدام node app.js، كانت العملية تتعطل بشكل متكرر بسبب استثناءات لم يتم التقاطها، مما تسبب في انقطاعات طويلة في الخدمة أثناء التشغيل غير المراقب. فقام بتطبيق PM2 لتمكين إعادة التشغيل التلقائي ووضع الكلستر، إلى جانب استخدام حاويات Docker ووكيل عكسي Nginx، مما أدى إلى رفع معدل توفر الخدمة من 95% إلى 99.9% — وأصبح أخيرًا قادرًا على النوم بهدوء.
ستتعلم:
- إدارة العمليات PM2 (بدء التشغيل / إعادة التشغيل / السجلات / monit)
- أساسيات دوكر (Dockerfile / docker-compose)
- إدارة متغيرات البيئة (.env / تكوين بيئة الإنتاج)
- وحدة المجموعة
- تحليل الأداء (console.time / performance hooks / clinic.js)
- تصميم معايير تقييم الفحص الصحي
- تكوين الوكيل العكسي لـ Nginx
1. إدارة العمليات PM2
(1) لماذا نحتاج إلى مدير عمليات؟
في بيئة الإنتاج، قد تتعطل عمليات Node.js بسبب استثناءات لم يتم التقاطها، أو تسربات في الذاكرة، أو عدم كفاية موارد النظام. إذا تم تشغيل العملية مباشرةً باستخدام node app.js، فلن يتم إعادة تشغيلها بعد التعطل، مما يؤدي إلى انقطاع الخدمة. يُعد PM2 أكثر برامج إدارة العمليات شيوعًا لـ Node.js، حيث يوفر ميزات إعادة التشغيل التلقائي، وإدارة السجلات، وموازنة الحمل، والمراقبة.
▶ مثال:(2) التثبيت والأوامر الأساسية
npm install -g pm2
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 ومراقبته
pm2 start app.js --name "charlie-api" -i max
pm2 monit
pm2 save
pm2 startup
┌─────┬──────────────┬─────────────┬─────────┬─────────┬──────────┐
│ 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
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
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 وتشغيلها
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
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) استراتيجية تكوين بيئة الإنتاج
.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 |
| مركز تكوين المنصة السحابية | مرتفع | مرتفع | ممتاز | المشاريع السحابية الأصلية |
▶ مثال: تحميل تكوينات متعددة البيئات
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) إعداد المجموعة يدويًّا
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 بدعم مدمج للمجموعات يمكن تفعيله دون الحاجة إلى تعديل الكود:
pm2 start app.js -i max
pm2 start app.js -i 4
▶ مثال: مقارنة أداء أوضاع المجموعات
node single.js &
ab -n 10000 -c 100 http://localhost:3000/
Single process:
Requests per second: 3254.21 [#/sec]
Cluster (4 workers):
Requests per second: 11280.67 [#/sec]
5. تحليل الأداء
(1) التوقيت الأساسي باستخدام console.time
أبسط طريقة لقياس الأداء، وهي مثالية لتحديد العمليات البطيئة بسرعة:
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 توقيتًا عالي الدقة:
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:
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 | المراقبة المستمرة | متوسطة | عالية |
▶ مثال: استخدام المدفع الآلي في اختبار الحمل
npm install -g autocannon
autocannon -c 100 -d 10 http://localhost:3000/api/health
┌─────────┬──────┬──────┬───────┬──────┬───────┬───────┬───────┐
│ 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) الفحص الصحي الأساسي
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
▶ مثال:(3) فحص صحي شامل
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) الفصل بين الجاهزية والحيوية
في بيئات الإنتاج، تنقسم فحوصات الحالة عادةً إلى نوعين: فحص الحيوية (ما إذا كانت العملية قيد التشغيل) وفحص الجاهزية (ما إذا كانت جاهزة لاستقبال حركة المرور):
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
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 في بيئة الإنتاج
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
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
# 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. مثال شامل: تكوين النشر الكامل
قام تشارلي بدمج جميع تكوينات النشر في نظام واحد، مما أدى إلى أتمتة العملية برمتها بدءًا من الكود وصولاً إلى مرحلة الإنتاج.
▶ مثال: هيكل مجلدات المشروع
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
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': '',
},
},
};
▶ مثال: ملف 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
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
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
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);
});
❓ أسئلة شائعة
س: أيهما يجب أن أستخدم، 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) النقاط الرئيسية في هذا الدرس
- يوفر PM2 ميزة إعادة التشغيل التلقائي للعمليات، ووضع الكتلة، وإدارة السجلات
- تضمن تقنية الحاويات في دوكر (Docker) اتساق البيئة وتوحيد عملية النشر
- تكوين منفصل عبر متغيرات البيئة، مع دعم التبديل بين بيئات متعددة
- يستفيد وضع الكلستر استفادة كاملة من المعالجات متعددة النوى لزيادة معدل الإنتاجية
- أدوات تتبع الأداء و Clinic.js: تحديد نقاط الاختناق في الأداء
- تُعد نقاط النهاية الخاصة بالفحص الصحي أساس تنسيق الحاويات وموازنة الأحمال
- الوكيل العكسي Nginx لبروتوكول SSL، وموازنة الأحمال، والموارد الثابتة
(2) الواجبات المنزلية
- ابدأ مشروع Express الخاص بك باستخدام PM2، وقم بتكوين
ecosystem.config.js، وقم بتمكين وضع الكلستر، واستخدمpm2 monitلمراقبة حالة التشغيل. - اكتب ملف Dockerfile للمشروع، واستخدم عمليات البناء متعددة المراحل لتحسين حجم الصورة، واستخدم
docker-composeلتشغيل Node.js وMongoDB في آن واحد - قم بتنفيذ نقاط النهاية الثلاث الخاصة بفحص الحالة
/healthو/healthzو/readyz، وقم بتكوينHEALTHCHECKفي Docker. - استخدم
autocannonلإجراء اختبار الحمل على واجهة برمجة التطبيقات (API) الخاصة بك، ومقارنة الفروق في معدل النقل بين وضع العملية الفردية ووضع المجموعة. - اكتب إعدادات الوكيل العكسي لـ Nginx لتنفيذ إنهاء SSL وموازنة الحمل، واستخدم
abأوcurlللتحقق من أن إعادة توجيه الوكيل تعمل بشكل صحيح.
📝 تمارين
- أكمل جميع أمثلة الأكواد الواردة في هذا الدرس وتأكد من أن كل منها يعمل بشكل صحيح.
- قم بتعديل المثال الشامل وأضف الإضافات الخاصة بك
- راجع الوثائق الرسمية، وحدد واجهة برمجة تطبيقات (API) واحدة أو اثنتين لم يتم تناولهما في هذا الدرس، واكتب كود اختبار لهما.
- التأمل: كيف ستطبق ما تعلمته في هذا الدرس على مشروع في الواقع العملي؟
- حاول أن تجمع بين ما تعلمته في هذا الدرس والمواد التي درستها في الدروس السابقة لإنشاء مشروع صغير.