English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

ضيف
1 / ?

طريقتان لتحمل المزيد من الأحمال

مرحباً

عندما يبدأ الخدمة بالتعثر تحت الحمل، يواجه المشغّل خياراً. إما تكبير الجهاز الحالي (مزيد من المعالج، مزيد من الذاكرة، أقراص أسرع). أو إضافة المزيد من الأجهزة التي يقوم كل منها بنفس العمل.

يُطلق على المسار الأول اسم التوسع الرأسي (التوسيع للأعلى). ويُطلق على الثاني اسم التوسع الأفقي (التوسيع للخارج).

تعلّم هذه الدرس لماذا تختار كل بنية تحتية ويب حديثة تقريباً التوسع الأفقي، وما هي خاصية من خصائص الحمل العملي التي تجعل هذا الاختيار قابلاً للتنفيذ. الإجابة مخبأة في كلمة واحدة: الحالة.

بحلول النهاية، ستفهم:

- منحنيات التكلفة للتوسع الرأسي مقابل الأفقي، ومتى يكون كل منهما مناسباً

- ما تعنيه مصطلحا "الحالة" (Stateful) و"بدون حالة" (Stateless) عملياً، ولماذا يمكن تضاعف أحدهما بكفاءة عالية

- الحسابات التي تحدد حجم أسطول النسخ (Replicas) تحت الأحمال المتوقعة والأحمال الطارئة

- قاعدة الهامش (Headroom) التي تمنع أي طبقة من الانهيار بعد تجاوز نقطة الانعطاف في الطابور

- أين يجب أن تقيم الحالة (فهي لا تختفي أبداً)، وكيفية إخراجها من الطبقات التي تحتاج إلى التوسع

لماذا يفوز التوسع الأفقي بعد تجاوز حد معين

التوسع الرأسي: صندوق أكبر

المزايا: بسيط. لا حاجة لتغييرات في الكود. لا حاجة للتنسيق. العملية نفسها تمتلك الآن موارد معالجة مركزية (CPU) أكبر.

العيوب: سقف محدود. أكبر آلة افتراضية (VM) متاحة تجاريًا تمتلك ذاكرة عشوائية (RAM) ونوى (Cores) محدودة. فوق هذا الحد، لا يمكن لأي مبلغ من المال شراء سعة إضافية. تتجاوز التكاليف النمو الخطي (Superlinearly) بعد تجاوز نقطة التوازن المثلى في عروض المزود. تعطل تلك الآلة الواحدة يؤدي إلى توقف الخدمة بالكامل.

التوسع الأفقي: العديد من الخوادم الأصغر

المزايا: لا يوجد سقف (إلى حد استعدادك لدفع تكاليف وتنسيق الخوادم). تزداد السعة خطيًا مع عدد النسخ (Replicas)، بشكل متوقع. تعطل نسخة واحدة يؤدي إلى فقدان 1/N من السعة، وليس 100%.

العيوب: يتطلب أن يدعم الحمل العملي (Workload) ذلك. بعض الأحمال (قاعدة بيانات كبيرة واحدة، خادم لعبة يحتوي على جلسات نشطة) يقاوم التوسع الأفقي. يصبح التنسيق وتوزيع الأحمال مخاوف تشغيلية.

نقطة التقاطع: أي خدمة إنتاجية تحتاج إلى البقاء على قيد الحياة في حال تعطل أي آلة واحدة يجب أن تعمل على جهازين على الأقل. بمجرد قبولك بوجود جهازين، تكون قد اخترت التوسع الأفقي بالفعل. من هناك، السؤال ليس "هل نفعل ذلك؟" بل "ما مدى رخص إضافة النسخة التالية؟"

العنصر المُمكّن الرئيسي: حمل عملي لا يحتفظ بأي حالة (State) لكل طلب على الآلة نفسها. ثم يمكن لأي نسخة الإجابة على أي طلب، وإضافة نسخة تزيد السعة دون الحاجة إلى تنسيق.

التوسع الرأسي مقابل الأفقي: منحنى التكلفة والسقف

تعمل مجموعة على خدمة على جهاز افتراضي واحد بـ 8 معالجات (CPU) تتعامل مع 800 طلب في الثانية عند استهلاك 60% من المعالج. يتوقعون أن تنمو حركة المرور 4 أضعاف خلال العام القادم. هم في نقاش: هل يستأجرون جهازًا افتراضيًا بـ 32 معالجًا (توسع رأسي)، أم يشغلون أربعة أجهزة افتراضية متطابقة بـ 8 معالجات خلف موزّع أحمال (توسع أفقي). أي خيار تنصح به، واسمِ سببين يتجاوزان السعة الخام.

الحالة (Stateful) مقابل بدون حالة (Stateless) في الممارسة

الحالة لا تختفي أبدًا، بل تنتقل فقط

مكوّن ذو حالة (Stateful): يحتفظ بمعلومات يؤدي فقدانها إلى تغيير في السلوك. قاعدة بيانات تحتفظ بحسابات المستخدمين. ذاكرة تخزين مؤقت تحتفظ برمز الجلسة. عامل (Worker) يثبّت اتصال بث طويل الأمد لمستخدم محدد.

مكوّن بلا حالة (Stateless): لا يحتفظ بأي معلومات يؤدي فقدانها إلى تأثير سلبي. طبقة ويب تقرأ طلبًا، تستعلم عن قاعدة بيانات، & تكتب استجابة. كل طلب قائم بذاته؛ الطبقة لا تتذكر شيئًا بين الطلبات.

رؤية أساسية: الحالة لا تختفي أبدًا من النظام. بل تنتقل إلى طبقة مصممة لحملها (قاعدة بيانات، عناقيد Redis، مخزن كائنات). يمكن للطبقات التي تواجه حركة المرور أن تصبح بلا حالة، & الطبقات بلا حالة تتوسع أفقيًا لأن أي نسخة (Replica) يمكنها الإجابة على أي طلب.

اختبار عملي: إذا قمت بإيقاف عملية واحدة عشوائيًا في هذه الطبقة & إعادة تشغيلها، هل سيواجه أي مستخدم إجابة خاطئة أو فقدان جلسة؟ إذا كان الجواب نعم، فهو يحتفظ بالحالة. إذا كان الجواب لا، فهو لا يحتفظ بها.

أمثلة

- عملية ويب بلغة بايثون تقرأ الطلبات، تستعلم عن Postgres، وتعيد JSON: بلا حالة. الحالة مقيمة في Postgres.

- عملية ويب بلغة بايثون تحتفظ بعربات التسوق للمستخدمين في الذاكرة المحلية: ذات حالة. إيقاف العملية يؤدي إلى فقدان العربات.

- خادم WebSocket يحتفظ بالاتصالات المفتوحة مع مستخدمي الدردشة: حالة (Stateful) بمعنى الاتصالات. إذا تم إنهاء العملية، ستُفقد الاتصالات؛ ويجب على العملاء إعادة الاتصال. غالبًا ما يمكن توسيع هذه الأنظمة أفقيًا مع الحذر (جلسات لاصقة، تجزئة متسقة).

- ذاكرة تخزين مؤقت Redis أمام Postgres: حالة (Stateful) لمحتويات ذاكرة التخزين المؤقت، لكنها مقبولة إذا كانت أخطاء عدم العثور في ذاكرة التخزين المؤقت (cache misses) مقبولة. تعني عطل في نسخة احتياطية (replica) عدم وجود في ذاكرة التخزين المؤقت، وليس فقدانًا للبيانات.

التصميم للتوسعة الأفقية = دفع الحالة (State) خارج الطبقة التي تحتاج إلى التوسعة.

تدقيق طبقة مشبوهة

تقوم فريق بتشغيل واجهة برمجة تطبيقات (API) للتوصيات على 6 خوادم افتراضية خلفية (VMs) خلف وكيل عكسي (reverse proxy). التطبيق: يقرأ معرف المستخدم من الطلب، يجلب النشاط الأخير للمستخدم من Postgres، يشغل خوارزمية التقييم، ويعيد قائمة بالعناصر الموصى بها. يوجد سلوكان غير قياسي:

- يحتفظ التطبيق بذاكرة تخزين مؤقت (cache) لـ "النشاط الأخير للمستخدم" في ذاكرة العملية (process memory)، يتم تعبئتها عند أول طلب للمستخدم، وتُعاد استخدامها في الطلبات اللاحقة.

- يستخدم التطبيق جلسات لاصقة (sticky sessions): بمجرد أن يصل المستخدم إلى الخادم الافتراضي رقم 3، تُوجَّه جميع طلباته اللاحقة إلى الخادم الافتراضي رقم 3 (تم تكوين الوكيل للتوجيه اللاصق بناءً على كوكي).

حدد أي من هذين السلوكين يجعل الطبقة "حالة" (stateful) وشرح ما الذي سينكسر إذا حاول الفريق التوسع من 6 خوادم افتراضية إلى 12. ثم اقترح إعادة تصميم تتيح للطبقة التوسع أفقيًا دون فقدان فائدة ذاكرة التخزين المؤقت للمستخدم.

معادلة النسخ (Replica)

أبسط معادلة سعة

بمجرد أن يصبح الطابق (Tier) بلا حالة (Stateless)، يصبح تحديد حجمه مسألة حسابية. تحتاج إلى عدد كافٍ من النسخ (Replicas) بحيث يصل الحمل في الحالة المستقرة ويغادر بنفس المعدل، مع وجود هامش أمان للذروات (Surge).

الصيغة:

replicas = ⌈ (peak_load × surge_factor) / per_replica_capacity ⌉ + headroom

حيث:

- peak_load: أقصى معدل للطلبات في الثانية (requests/second) المتوقع استدامته في التشغيل الطبيعي

- surge_factor: معامل مضاعف يغطي الاندفاعات القصيرة فوق الذروة (عادةً 1.5x إلى 2x للحركة المرورية المتوقعة، و3x أو أكثر للحركة الفيروسية / غير المتوقعة)

- per_replica_capacity: عدد الطلبات في الثانية التي تعالجها نسخة واحدة عند زمن استجابة (Latency) ومستوى استخدام مقبولين (يُقاس عادةً عند 70% من المعالج، وليس عند التشبع الكامل)

- headroom: نسخ إضافية بحيث لا يؤدي تعطل عدد قليل من النسخ إلى انهيار الطابق (عادةً 1-2 نسخة للأساطيل الصغيرة، و10-20% للأساطيل الأكبر)

مثال محلول: يتعامل خادم خلفي مع 100 طلب/ثانية عند استهلاك 70% من المعالج لكل نسخة. الحمل الذروي هو 600 طلب/ثانية. تتوقع حدوث قفزات عابرة بمقدار ضعف الحمل. تريد البقاء قادراً على العمل في حال تعطل نسختين.

عدد النسخ = ⌈ (600 × 2) / 100 ⌉ + 2 = 12 + 2 = 14 نسخة

قاعدة الـ 80%

سعة كل نسخة ليست نقطة التشبع. قِس السعة عند استهلاك 70-80% من المعالج، وليس عند 100%.

فوق مستوى استغلال يبلغ حوالي 80%، ترتفع منحنيات الطوابير بشكل حاد: طابور كان يستغرق 10 مللي ثانية عند استغلال 60% يستغرق 80 مللي ثانية عند استغلال 90%. زمن الاستجابة (Latency)، وليس معدل الإنتاجية (Throughput)، هو ما ينكسر أولاً. (يشتق درس المصاحب geometry_of_stateless_horizontal_scaling هذه المنحنى رياضياً.)

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

الثابت: خصّص الموارد للحمل الذروي مع هامش للقفزات، وقبل تكلفة التشغيل بمستوى استغلال منخفض خارج أوقات الذروة.

التوسع التلقائي: يضيف متحكم (Controller) نسخاً ويحذفها بناءً على مستوى الاستغلال المرصود، أو زمن الاستجابة المستهدف، أو عمق الطابور.

تنبيه حول التوسع التلقائي: وقت بدء التشغيل البارد (cold-start) مهم. إذا استغرق تشغيل نسخة جديدة (replica) دقيقتين، فلا يمكن للتوسع التلقائي أن يستجيب لارتفاع مفاجئ في الطلبات يستمر 30 ثانية. يحافظ التوسع التلقائي الناضج على مجموعة دافئة (warm pool) من النسخ المُجهَّزة مسبقاً، وتكون هذه المجموعة أقل بقليل من عتبة التوسع للأعلى.

معادلة تحديد حجم النسخ مع مثال محلول

تحديد حجم أسطول الخدمة الجديدة

تخطط فرقكم لإطلاق واجهة برمجة تطبيقات (API) لبيانات الفيديو الوصفية (metadata). تُظهر الاختبارات القياسية أن نسخة واحدة تتعامل مع 250 طلباً في الثانية عند استهلاك 70% من المعالج (CPU) وزمن استجابة p99 يبلغ 50 مللي ثانية. تتوقع إدارة التسويق أن يبلغ ذروة الحمل 4,000 طلب في الثانية خلال ساعات الذروة. وقد يؤدي حدث ترويجي مخطط له إلى ارتفاع مؤقت في الطلبات يصل إلى 3 أضعاف الذروة. تريدون أن تبقى الخدمة صالحة للعمل في حال تعطل 3 نسخ في نفس الوقت، دون أن يتجاوز استهلاك المعالج على النسخ المتبقية 80%.

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

البداية الباردة، والتفريغ البطيء، وحواف واقعية أخرى

الأساطيل الحقيقية لها حواف واقعية

تفترض الصيغة أن النسخ (Replicas) تظهر فوراً، وتستقبل الحركة فوراً، وتُفرّغ الحركة فوراً. لا شيء من ذلك يتحقق في بيئة الإنتاج.

البداية الباردة: يحتاج أيّ نسخة جديدة إلى إقلاع نظام التشغيل، وإطلاق العملية، وتحميل الإعدادات، وإحماء الذاكرة المؤقتة (Caches)، واجتياز فحوصات الصحة. قد يستغرق ذلك من 5 ثوانٍ (إعادة تشغيل حاوية) إلى 5 دقائق (إقلاع كامل لآلة افتراضية + سحب الصورة). لا يمكن للتوسيع التلقائي (Autoscaling) الاستجابة للذروات (Bursts) الأقصر من هذا التأخير.

التفريغ البطيء: تحتاج النسخة التي يتم إزالتها من الحوض (Pool) إلى وقت لإنهاء الطلبات الجارية قبل الإنهاء. وإلا سيشهد المستخدمون استجابات مبتورة. تدعم الوكلاء العكسيين (Reverse Proxies) خاصية التفريغ (إيقاف قبول الطلبات الجديدة، وإنهاء الطلبات النشطة)، لكن ذلك يستغرق من ثوانٍ إلى دقائق.

حوض الدفء (Warm pool): تحافظ أساطيل الإنتاج على حوض من النسخ المكررة المُجهّزة مسبقًا لكنها خاملة، وجاهزة لاستقبال الحركة المرورية عند صدور الإشارة. يقوم هذا النهج بتضحية بتكلفة ثابتة صغيرة مقابل استجابة سريعة للزخم المفاجئ.

تصريف الاتصالات مقابل الإيقاف الفوري: يُعدّ الإيقاف السليم أمرًا بالغ الأهمية. إشارة SIGTERM التي تُطلق عملية التصريف تستغرق وقتًا أطول من SIGKILL، لكنها لا تُكسر طلبات المستخدم.

نافذة فحص الصحة: قد تمرّ نسخة مكررة للتو بعد بدء تشغيلها بفحص الصحة الأولي قبل أن يصبح حوض اتصالات قاعدة البيانات الخاص بها "دافئًا"؛ ثم يرسل الوسيط (proxy) الحركة المرورية الفعلية، فتكون الطلبات الأولى (حوالي اثني عشر طلبًا) بطيئة. اضبط فحوصات الصحة لاختبار المسار الفعلي، وليس مجرد بقاء العملية حية.

زحف الالتصاق (Stickiness creep): حتى الطبقات التي يُفترض أنها بلا حالة (stateless) تكتسب خاصية "الالتصاق" مع مرور الوقت (مثل ذاكرة التخزين المؤقت لـ CDN، وذاكرة التخزين المؤقت لمحلل DNS، وحُسن اتصالات). كن مشككًا في "النسخ المكررة المتطابقة" التي تتصرف بشكل مختلف رغم ذلك.

حوض الدفء أم التوسع التلقائي التفاعلي؟

يتعرض واجهة برمجة التطبيقات (API) الخاصة ببيانات الفيديو الوصفية (نفس الواجهة من السؤال السابق، والمقاسة بـ 51 نسخة مكررة للذروة المستقرة + الزخم) لزخم مفاجئ لمدة 30 ثانية يصل إلى 5 أضعاف الحمل العادي كلما تم رفع فيديو فيروسي جديد. يستغرق التوسع التلقائي حاليًا 90 ثانية لإضافة نسخة مكررة جديدة من الصفر (سحب الصورة + التسخين الأولي). خلال الفجوة البالغة 90 ثانية، يرتفع زمن الاستجابة (Latency) بشكل حاد وتنتهي مهلة بعض الطلبات (Time out).

اقترح حلًا. اختر: (أ) الحفاظ على التوسع التلقائي التفاعلي لكن مع ضبطه بشكل مختلف، أو (ب) تجهيز حوض دفء من النسخ المكررة الخاملة، أو (ج) التخصيص الثابت (Static-provision) للذروة البالغة 5 أضعاف بشكل دائم. برر اختيارك واذكر تكلفة واحدة ستفرضها الخيارات الأخرى.

تصميم طبقة بلا حالة ضمن قيود محددة

التوليف

لقد تعلمت لماذا يفوز التوسع الأفقي بعد تجاوز عتبة صغيرة، وماذا تعني "الحالة" عملياً، وكيف تحجم أسطولاً تحت الحمل المتوقع وحمل الذروة، وأين ينكسر التوسع الأفقي عند الحواف.

طبّق جميع العناصر الأربعة.

صمّم طبقة خلفية (backend tier) لـ feed.example.com، وهي واجهة برمجة تطبيقات (API) لخدمة تغذية اجتماعية. القيود: سعة كل نسخة (replica) هي 200 طلب/ثانية عند 70% من استخدام المعالج (CPU)؛ الحمل الذروي المتوقع 1500 طلب/ثانية؛ عامل الذروة (surge factor) 2.5x (قصص رائجة بشكل عرضي)؛ يجب أن تتحمل النظام 2 حالات فشل متزامنة للنسخ؛ زمن البدء البارد (cold-start) 60 ثانية؛ يمكن أن تستمر الذروات (bursts) لمدة 45 ثانية؛ الميزانية تسمح بقدرة خاملة (idle capacity) ولكن لا تسمح بتخصيص دائم بمقدار 2.5x.

حدّد حجم الأسطول المستقر (steady fleet) (أظهر الحسابات)، واختر استراتيجيتك للتعامل مع الذروات (مخزون دافئ / توسيع ذاتي تفاعلي / كلاهما)، وحدّد عنصرًا واحدًا من حالة المستخدم (per-user state) الذي من المرجح أن تحتفظ به التطبيق، وأين يجب أن ينتقل هذا العنصر للحفاظ على خلوّ الطبقة من الحالة (stateless).

إلى أين يتجه هذا الدرس بعد ذلك

إلى أين تتجه هذه الدورة بعد ذلك

لديك الآن نموذج ذهني عامل لطبقة بلا حالة: لماذا تتوسع، وكيف تحدد حجمها، وما الذي ينكسر عند حوافها، وأين يجب أن تنتقل الحالة عندما تدفعها خارج الطبقة التي تحتاج إلى النمو.

يتناول الدرس التالي في هذه الدورة (cs_distsys_ingress_egress_separation) مشكلة أدق: حتى الطبقة بلا الحالة المُحسوبة حجمها بشكل مثالي يمكن أن تفشل بطرق مفاجئة عندما يتشارك المرور الوارد والصادر نفس المسار الشبكي. يتضمن المثال الكلاسيكي وكيلًا (proxy) يحاول الاتصال بنفسه؛ ويتضمن الحل تقسيم طبقة واحدة إلى طبقتين بمسؤوليات مختلفة.

الدرس المصاحب: geometry_of_stateless_horizontal_scaling يستنتج منحنى الطابور، وقانون ليتل (Little's Law) المطبق على أسطول النسخ، والمعنى الهندسي لنقطة الانحناء عند 80% من الاستخدام.

أحسنت. إلى الأمام.