كيف تقوم بتحديث الخدمة دون إيقاظ المستخدمين؟
تخيل: يوم الجمعة، في المساء، قررت نشر إصلاح سريع للخلل على الإنتاج. تضغط على deploy - وتتعطل خدمة المراسلة الفورية أو السينما عبر الإنترنت أو CRM لمدة 30 ثانية. آلاف المستخدمين في هذه اللحظة يرون 502 Bad Gateway. وإذا كانت بوابة دفع؟ احسب الخسائر.
النشر بدون توقف (Zero-downtime deployment) ليس رفاهية، بل معيار لخدمات TypeScript الحديثة. في هذه المقالة، سنشرح التقنيات الحقيقية المستخدمة في الإنتاج: من الإنهاء المهذب إلى النشر الأزرق-الأخضر والتحديث المتدرج. ستحصل على أنماط جاهزة للتطبيق بلغة TypeScript.
لماذا النشر العادي لتطبيق TypeScript خطير؟
عند تشغيل npm run build && node dist/index.js، تحدث عدة مشاكل:
- قطع الاتصالات - يتم قتل العملية القديمة بينما لا تزال الطلبات الجديدة قادمة.
- فقدان البيانات في الذاكرة - الجلسات غير المحفوظة، ذاكرة التخزين المؤقت، قوائم الانتظار.
- بداية قذرة - العملية الجديدة لم تسخن بعد، ولكنها تتلقى حركة المرور بالفعل.
في TypeScript (Node.js) تتفاقم المشكلة بسبب عدم التزامن: لا تعرف أي الوعود (promises) لا تزال قيد التنفيذ. لكن هناك حل.
التقنية #1: الإنهاء المهذب (Graceful Shutdown)
أساس النشر بدون توقف هو المعالجة الصحيحة لإشارات نظام التشغيل. عندما يقرر المنظم (Kubernetes, PM2) إيقاف خدمتك، يرسل SIGTERM. يجب أن يتوقف كودك عن قبول الطلبات الجديدة، وينتظر اكتمال الطلبات الحالية، ثم يموت فقط.
التنفيذ بلغة TypeScript مع Express
import express, { Request, Response } from 'express';import http from 'http';
const app = express();let server: http.Server;
app.get('/health', (req: Request, res: Response) => { res.status(200).json({ status: 'ok' });});
// إشارة من Docker/K8sprocess.on('SIGTERM', async () => { console.log('تم استلام SIGTERM، بدء الإنهاء المهذب...'); // 1. التوقف عن الاستماع للاتصالات الجديدة server.close(() => { console.log('تم إغلاق خادم HTTP'); // 2. إغلاق الاتصالات بقاعدة البيانات، Redis إلخ. // await db.disconnect(); process.exit(0); });
// 3. إعطاء وقت للطلبات الحالية (مهلة 30 ثانية) setTimeout(() => { console.error('إنهاء إجباري بسبب انتهاء المهلة'); process.exit(1); }, 30000);});
const PORT = process.env.PORT || 3000;server = app.listen(PORT, () => { console.log(`الخدمة تعمل على المنفذ ${PORT}`);});هام: في TypeScript استخدم http.Server من الوحدة http، وليس نوع Express. طريقة server.close() تعالج اتصالات keep-alive بشكل صحيح.
التقنية #2: التحديث المتدرج (Rolling Update)
إذا كان لديك عدة نسخ من الخدمة (3-5 مثيلات قيد التشغيل)، فإن التحديث المتدرج يقوم بتحديثها واحدة تلو الأخرى. بينما يتم إعادة تشغيل واحدة، تستقبل الأخرى حركة المرور.
مثال لـ Kubernetes Deployment
# deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: my-typescript-servicespec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 # كم عدد البودات التي يمكن أن تكون غير متاحة maxSurge: 1 # كم عدد البودات الجديدة التي يمكن إنشاؤها فوق الخطة template: spec: containers: - name: app image: myregistry/service:v2 ports: - containerPort: 3000 livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 3 periodSeconds: 5أضف إلى تطبيق TypeScript الخاص بك نقطة نهاية /ready التي ترجع 200 فقط عندما تكون الخدمة جاهزة لاستقبال حركة المرور.