النشر والإنتاج
نشر تطبيقات WebSocket يتطلب اعتبارات خاصة تتجاوز تطبيقات الويب التقليدية. في هذا الدرس، سنغطي كل ما تحتاج معرفته حول نقل تطبيق الوقت الفعلي الخاص بك إلى الإنتاج.
اعتبارات الإنتاج
قبل النشر، تأكد من أن تطبيقك جاهز للإنتاج:
الاختلافات الرئيسية: تطبيقات WebSocket تحتفظ بالاتصالات المستمرة، تتطلب توكيلاً صحيحاً، تحتاج إلى إدارة العمليات، ولها متطلبات توسع مختلفة عن تطبيقات HTTP التقليدية.
إعداد PM2 لإدارة العمليات
PM2 هو مدير عمليات إنتاج لتطبيقات Node.js يحافظ على تشغيل خادم WebSocket الخاص بك:
# تثبيت PM2 عالمياً
npm install -g pm2
# بدء التطبيق الخاص بك
pm2 start server.js --name "websocket-server"
# عرض العمليات الجارية
pm2 list
# عرض السجلات
pm2 logs websocket-server
# مراقبة الموارد
pm2 monit
أنشئ ملف نظام PM2 لتكوين أفضل (ecosystem.config.js):
module.exports = {
apps: [{
name: 'websocket-server',
script: './server.js',
instances: 1,
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm Z',
max_memory_restart: '1G',
watch: false,
autorestart: true,
max_restarts: 10,
min_uptime: '10s'
}]
};
ابدأ باستخدام ملف النظام:
pm2 start ecosystem.config.js
pm2 save
pm2 startup
نصيحة: استخدم pm2 save وpm2 startup للتأكد من أن تطبيقك يعيد التشغيل تلقائياً بعد إعادة تشغيل الخادم.
تكوين Nginx كوكيل WebSocket
يمكن لـ Nginx توكيل اتصالات WebSocket إلى خادم Node.js الخاص بك. أنشئ تكوين Nginx:
# /etc/nginx/sites-available/websocket-app
upstream websocket_backend {
server localhost:3000;
keepalive 64;
}
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
# رؤوس ترقية WebSocket
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_connect_timeout 7d;
proxy_send_timeout 7d;
proxy_read_timeout 7d;
}
}
قم بتمكين الموقع وإعادة تشغيل Nginx:
sudo ln -s /etc/nginx/sites-available/websocket-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
تحذير: يجب أن يكون proxy_read_timeout أطول من فاصل ping/pong الخاص بـ WebSocket لمنع المهلات.
تمكين SSL/TLS لـ WebSockets الآمنة (WSS)
يجب أن تستخدم خوادم WebSocket الإنتاجية wss:// (WebSocket الآمن). قم بتثبيت Certbot لشهادات SSL من Let's Encrypt:
# تثبيت Certbot
sudo apt install certbot python3-certbot-nginx
# الحصول على شهادة SSL
sudo certbot --nginx -d yourdomain.com
# التجديد التلقائي مكون تلقائياً
sudo certbot renew --dry-run
قم بتحديث تكوين Nginx لـ HTTPS:
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://websocket_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;
}
}
# إعادة توجيه HTTP إلى HTTPS
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
قم بتحديث كود العميل الخاص بك لاستخدام WSS:
const socket = io('https://yourdomain.com', {
transports: ['websocket', 'polling'],
secure: true
});
تكوين البيئة
استخدم متغيرات البيئة للتكوين. أنشئ ملف .env:
NODE_ENV=production
PORT=3000
REDIS_HOST=localhost
REDIS_PORT=6379
CORS_ORIGIN=https://yourdomain.com
JWT_SECRET=your-secret-key-here
DATABASE_URL=postgresql://user:pass@localhost:5432/dbname
قم بتحميل متغيرات البيئة في تطبيقك:
require('dotenv').config();
const PORT = process.env.PORT || 3000;
const REDIS_HOST = process.env.REDIS_HOST || 'localhost';
const io = require('socket.io')(server, {
cors: {
origin: process.env.CORS_ORIGIN,
credentials: true
}
});
تنفيذ فحوصات الصحة
أضف نقاط نهاية فحص الصحة للمراقبة:
const express = require('express');
const app = express();
app.get('/health', (req, res) => {
const health = {
uptime: process.uptime(),
timestamp: Date.now(),
status: 'ok',
connections: io.engine.clientsCount
};
res.json(health);
});
app.get('/ready', async (req, res) => {
try {
// فحص اتصال Redis
await redisClient.ping();
// فحص اتصال قاعدة البيانات
await db.query('SELECT 1');
res.status(200).json({ status: 'ready' });
} catch (error) {
res.status(503).json({ status: 'not ready', error: error.message });
}
});
const server = app.listen(PORT);
const io = require('socket.io')(server);
الإيقاف التدريجي
نفذ الإيقاف التدريجي لإغلاق الاتصالات بشكل صحيح:
let isShuttingDown = false;
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
async function gracefulShutdown() {
if (isShuttingDown) return;
isShuttingDown = true;
console.log('بدء الإيقاف التدريجي...');
// إيقاف قبول اتصالات جديدة
server.close(() => {
console.log('تم إغلاق خادم HTTP');
});
// إخطار جميع العملاء المتصلين
io.emit('server:shutdown', {
message: 'الخادم قيد الإيقاف، يرجى إعادة الاتصال'
});
// امنح العملاء وقتاً لقطع الاتصال
setTimeout(() => {
// إغلاق جميع اتصالات Socket.io
io.close(() => {
console.log('تم إغلاق خادم Socket.io');
});
// إغلاق اتصال Redis
redisClient.quit();
// إنهاء العملية
process.exit(0);
}, 5000);
}
المراقبة باستخدام Prometheus
قم بتثبيت مقاييس Prometheus للمراقبة:
npm install prom-client
قم بإعداد جمع المقاييس:
const promClient = require('prom-client');
// إنشاء سجل
const register = new promClient.Registry();
// إضافة المقاييس الافتراضية
promClient.collectDefaultMetrics({ register });
// مقاييس مخصصة
const connectedClients = new promClient.Gauge({
name: 'websocket_connected_clients',
help: 'عدد عملاء WebSocket المتصلين',
registers: [register]
});
const messagesTotal = new promClient.Counter({
name: 'websocket_messages_total',
help: 'إجمالي عدد رسائل WebSocket',
labelNames: ['event'],
registers: [register]
});
// تحديث المقاييس
io.on('connection', (socket) => {
connectedClients.inc();
socket.on('disconnect', () => {
connectedClients.dec();
});
socket.onAny((event) => {
messagesTotal.inc({ event });
});
});
// نقطة نهاية المقاييس
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
أفضل ممارسات التسجيل
استخدم التسجيل المنظم للإنتاج:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// التسجيل في الإنتاج
if (process.env.NODE_ENV === 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
// استخدام المسجل
io.on('connection', (socket) => {
logger.info('عميل متصل', {
socketId: socket.id,
userId: socket.user?.id
});
});
قائمة فحص النشر
قائمة فحص ما قبل النشر:
- ✓ متغيرات البيئة مكونة
- ✓ شهادات SSL مثبتة (WSS)
- ✓ Nginx مكون كوكيل عكسي
- ✓ PM2 مكون مع ملف النظام
- ✓ نقاط نهاية فحص الصحة منفذة
- ✓ الإيقاف التدريجي منفذ
- ✓ التسجيل مكون
- ✓ جمع المقاييس ممكّن
- ✓ CORS مكون بشكل صحيح
- ✓ تحديد المعدل ممكّن
- ✓ قواعد الجدار الناري مكونة
- ✓ استراتيجية النسخ الاحتياطي موجودة
تمرين: قم بنشر تطبيق Socket.io على خادم إنتاج. قم بتكوين Nginx كوكيل عكسي مع تمكين SSL/TLS. قم بإعداد PM2 لإدارة العمليات. نفذ فحوصات الصحة والإيقاف التدريجي. أضف مقاييس Prometheus وأنشئ لوحة معلومات لمراقبة العملاء المتصلين وإنتاجية الرسائل. اختبر النشر من خلال الاتصال من عملاء متعددين ومراقبة السجلات.