هندسة الفوضى والمرونة

أنماط الإخفاق في الأنظمة الموزعة

18 دقيقة الدرس 3 من 27

أنماط الإخفاق في الأنظمة الموزعة

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

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

المغالطات الثماني وتكلفتها

كل مغالطة افتراض يبدو معقولاً أثناء التطوير المحلي ويُسبّب مفاجآت كارثية في الإنتاج.

  1. الشبكة موثوقة. في الواقع، تُفقد الحزم، وتفشل بطاقات الشبكة، وتُعاد تشغيل المحولات، وتُهاجر hypervisors السحابية الأجهزة الافتراضية في منتصف الطلب. تُخفي عمليات إعادة الإرسال TCP كثيراً من هذه الأحداث، لكن إعادة الإرسال تُضيف تأخيراً — مكالمة 5 مللي ثانية تصبح 2 ثانية تحت الاختناق. كل مكالمة RPC يجب أن تتسامح مع الفشل وتُعيد المحاولة مع التراجع الأسي بإضافة اهتزاز عشوائي.
  2. زمن الاستجابة صفر. خدمة تستدعي قاعدة بيانات وذاكرة تخزين مؤقت وواجهتَي برمجة تطبيقات منبثقتين في كل طلب تتراكم فيها التأخيرات: 20 + 35 + 15 + 40 = 110 مللي ثانية كحد أدنى، قبل توقفات GC وتأخيرات الذيل. قِس كل مكالمة عبر العمليات وخصص ميزانية لها؛ لا تفترض أبداً أنها لا تُذكر.
  3. النطاق الترددي لا نهائي. جلب 50 ميغابايت من الإعدادات عند كل بداية، أو توزيع طلب واحد على 1,000 شريحة، أمر جيد في الاختبارات المعيارية. في الإنتاج أثناء إعادة التشغيل المتدرجة، يُشبع الروابط الصاعدة، يُشغّل التحكم في الازدحام، ويُسبّب مهلات متسلسلة. الضغط العكسي والترقيم الصفحي والبث متطلبات إنتاج، لا تحسينات اختيارية.
  4. الشبكة آمنة. في شبكة الخدمات المصغّرة، تتواصل الخدمات عبر نفس قطاع الشبكة الذي يصله الحركة الخارجية ما لم يُفرض mTLS. المهاجم الذي يسيطر على حاوية واحدة يستطيع التحرك جانبياً دون سياسة شبكة. يجب تصميم الأمان من البداية — لا يمكن افتراضه من الطبقة التحتية.
  5. الطوبولوجيا لا تتغير. تتغير عناوين IP. تُجدوَل الحاويات من جديد. تُستبدل الأمثلة. خدمة تُضمّن عنوان IP في الكود تنكسر بصمت لحظة إعادة جدولة Kubernetes لتلك الخدمة. استخدم DNS أو شبكة الخدمات — لا تُضمّن عناوين أبداً في ملفات الإعداد.
  6. يوجد مسؤول واحد. في منظمة الخدمات المصغّرة، تملك 30 فريقاً 30 خدمة بجداول نشر وإجراءات حوادث مختلفة. افتراض أن سلطة واحدة يمكنها تنسيق حادثة متعددة الفرق في وقت حقيقي وهم. صمّم لأدلة تشغيل مستقلة وقواطع دائرة آلية لا تحتاج تنسيقاً بشرياً.
  7. تكلفة النقل صفر. تسلسل JSON ومصافحات TLS وإعداد الاتصال تستهلك CPU ووقتاً. خدمة تُجري 10,000 مكالمة RPC صغيرة في الثانية قد تُنفق جزءاً كبيراً من CPU على التسلسل وحده. المعالجة الدفعية وتجميع الاتصالات والبروتوكولات الثنائية (gRPC/Protobuf) تُعالج هذا مباشرة.
  8. الشبكة متجانسة. مسار الطلب يعبر عادةً طبقة Kubernetes overlay وVPC السحابية وربما VPN إلى منطقة أخرى وشبكة مزود خدمة الإنترنت للعميل. اختلافات MTU تُسبّب التجزئة؛ كل قطاع له معدل فقدان مختلف. رقم متوسط تأخير واحد يُخفي هذا التباين بالكامل.

فئات الإخفاق الرئيسية في الإنتاج

صنّفت التجربة التشغيلية في الشركات التي تُشغّل آلاف الخدمات مشهد الإخفاقات إلى عدد محدود من الفئات المتكررة. لكل فئة بصمة مميزة في إشارات المراقبة ونمط محدد لنطاق الانفجار يجب أن تُصمَّم تجارب الفوضى لكشفه.

1. إخفاقات الاصطدام (Crash Failures)

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

2. إخفاقات الإغفال (Omission Failures)

عقدة تتلقى طلباً لكنها لا تُنتج استجابة — تُسقط الرسائل بصمت. هذا لا يمكن تمييزه من منظور المستدعي عن الاصطدام، إلا أن الخادم لا يزال حياً ويستهلك الموارد. الأسباب تشمل المخازن المؤقتة للاستقبال المُثقَلة، وتشبع طابور TCP للنواة، والكود غير المتزامن المعيب الذي يفقد المهام عند امتلاء القناة. إخفاقات الإغفال خطيرة لأن الخادم قد يُعالج الطلبات جزئياً، مُنتجاً حالة غير متسقة لا يكتشفها أي مستدعٍ حتى تفشل المنطق التجاري.

3. الإخفاقات البيزنطية (Byzantine Failures)

عقدة تستجيب ببيانات خاطئة — نتائج غلط أو حمولات فاسدة أو استجابات تبدو صحيحة نحوياً لكنها خاطئة دلالياً. قد تُعيد عقدة بيزنطية بيانات قديمة من مسح ذاكرة تخزين مؤقت فاشل، أو تُجزّئ كتابة بصمت، أو تُعيد HTTP 200 لعملية لم تُحفظ أبداً. هذه هي أصعب فئة إخفاق للكشف: فحوصات الصحة تنجح، معدلات الأخطاء طبيعية، زمن الاستجابة p99 عادي. تظهر البصمة فقط في مقاييس الأعمال — أرصدة حسابات خاطئة، بنود طلبات مفقودة، إشعارات مكررة. مسبارات اتساق البيانات من طرف إلى طرف هي فئة تجارب الفوضى التي تستهدف هذه الفئة تحديداً.

4. إخفاقات التوقيت (Timing Failures)

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

5. تقسيم الشبكة (Network Partition)

تنقسم الشبكة: بعض العقد يمكنها الوصول لبعضها لكن لا يمكنها الوصول للأخرى. تضمن نظرية CAP أنه في حالة تقسيم يجب الاختيار بين الاتساق (رفض الكتابات التي لا يمكن نسخها بالكامل) والتوفر (قبول كتابات قد تتباين). معظم أنظمة الإنتاج — Kafka في الوضع غير المتزامن، DynamoDB مع الاتساق النهائي، معظم قواعد البيانات مع النسخ المتماثل غير المتزامن — تختار التوفر وتتسامح مع التباين المؤقت. تجارب الفوضى التي تُحاكي التقسيم تكشف ما إذا كان التباين يُعالَج بلطف أم يُنتج تلف بيانات الدماغ المنقسم.

Distributed System Failure Classes — Crash, Omission, Timing, Byzantine, Partition Failure Classes in a Distributed System Client upstream caller Network packets / partition Node A Healthy — responds OK Node B Crash / no reply Node C Omission — silent drop Node D Timing — too slow Node E Byzantine — wrong data Network Partition boundary
خمس فئات إخفاق على عقد مختلفة: A بصحة جيدة؛ B تتعطل (لا رد)؛ C تُغفل (حية لكن تُسقط الرسائل)؛ D تتجاوز المهلة (بيانات صحيحة، بطيئة جداً)؛ E بيزنطية (تستجيب ببيانات خاطئة). خط التقسيم يمكن أن يقطع الوصول إلى أي مجموعة فرعية.

نمط الإخفاق المتسلسل

في الإنتاج، نادراً ما تبقى إخفاقات العقدة الواحدة محصورة. النمط الذي يُسقط الخدمات على نطاق واسع هو الإخفاق المتسلسل (Cascading Failure): منبثق بطيء يُسبّب توقف خيوط المرحلة الأعلى في الانتظار، مما يستنفد تجمع الخيوط، مُسبّباً تراكماً في قائمة الانتظار، مُشغّلاً ضغطاً على الذاكرة، مُشغّلاً توقفات GC، مما يجعل عقدة المرحلة الأعلى نفسها بطيئة — مُنشئاً نشر الإخفاق صعوداً عبر مجموع مكدس الاستدعاء. حللت Netflix هذا النمط بعد انقطاع عيد الميلاد عام 2011 وبنت Hystrix لمعالجته؛ أنماط Resilience4j هي حل الجيل الحالي.

# كشف تسلسل جارٍ بواسطة استعلامات Prometheus المترابطة. # يجب أن تُشتعل الثلاثة في نفس الوقت — db أولاً، ثم payment، ثم gateway. # الخطوة 1: قاعدة بيانات بطيئة (السبب الجذري) histogram_quantile(0.99, rate(db_query_duration_seconds_bucket{ service="payment-db" }[5m]) ) > 0.5 # الخطوة 2: ارتفاع p99 في payment-service بسبب قاعدة البيانات البطيئة histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{ service="payment-service" }[5m]) ) > 2.0 # الخطوة 3: تدهور api-gateway بسبب بطء payment-service histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{ service="api-gateway", upstream="payment-service" }[5m]) ) > 3.0 # حين تُشتعل الثلاثة معاً بترتيب تصاعدي للكمون، # لديك تسلسل: أصلح قاعدة البيانات — الباقي يتعافى تلقائياً # بمجرد استنزاف تجمعات الخيوط.

استنفاد الموارد وقطيع الرعد

نمطان إضافيان يستحقان الاهتمام لأنهما يُشغَّلان بشكل موثوق من تجارب الفوضى ومن الحوادث الحقيقية على حد سواء.

يحدث استنفاد الموارد حين تُستهلك موارد مشتركة — تجمع خيوط، تجمع اتصالات، حد واصف الملفات، الكومة — بالكامل. أول علامة هي زيادة في زمن الاستجابة (الانتظار في طابور للحصول على المورد)، لا الأخطاء. بحلول ظهور الأخطاء، يكون الاستنفاد قد يبنى لدقائق. ulimit -n وكومة JVM وأحجام تجمع اتصالات قاعدة البيانات هي أكثر نقاط الاستنفاد شيوعاً في خدمات الإنتاج المصغّرة.

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

# كشف قطيع الرعد: ارتفاع حاد في التزامن بعد إعادة التشغيل. # ينتهر هذا التنبيه PromQL حين يرتفع معدل الطلب أكثر من 3x عن الأساس # في نافزة دقيقة واحدة — بصمة عمليات إعادة المحاولة المتزامنة. ( rate(http_requests_total{service="checkout-service"}[1m]) / rate(http_requests_total{service="checkout-service"}[10m] offset 1m) ) > 3.0

لماذا تصنيف أنماط الإخفاق مهم لتصميم الفوضى

مطابقة تجربة الفوضى لفئة الإخفاق الصحيحة هو ما يُميّز تجربة توليد إشارة عن ضوضاء. قتل حاوية يختبر مرونة إخفاق الاصطدام. إضافة 200 مللي ثانية من الكمون إلى مكالمة منبثقة يختبر انتشار إخفاق التوقيت. تلف مجموعة فرعية من استجابات الواجهة يختبر كشف الإخفاقات البيزنطية. إسقاط حزم بين منطقتي توفر يختبر معالجة التقسيم.

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

ممارسة احترافية — ضع خريطة لفئات الإخفاق على جرد خدماتك قبل تخطيط التجارب. لكل خدمة حرجة، حدد فئة الإخفاق التي هي أكثر عرضة لها، لا مجرد الأسهل حقناً. الخدمة ذات الحالة مع البيانات المنسوخة (قاعدة بيانات، Kafka، etcd) أكثر عرضة للإخفاقات البيزنطية وتلف الدماغ المنقسم عند التقسيم. الخدمة ذات التوزيع العالي (بوابة API تستدعي 20 خدمة) أكثر عرضة لإخفاقات التوقيت المتسلسلة. الخدمة كثيفة الجلسات أكثر عرضة لقطعان الرعد عند إعادة التشغيل. استهدف فئة الأعلى مخاطرة أولاً.
مصيدة إنتاجية — الخلط بين التوفر والصحة. تتتبع SLOs معدل نجاح الطلبات وزمن الاستجابة، لكن الإخفاقات البيزنطية تترك كلا المقياسين دون تأثير بينما تُفسد البيانات بصمت. يمكن أن يكون النظام أخضر تماماً في Prometheus بينما يُقدّم أرصدة حسابات خاطئة. هذا هو السبب في أن برامج الفوضى على مستوى الإنتاج تتضمن مسبارات الصحة — معاملات اصطناعية تكتب قيمة معروفة وتقرأها مرة أخرى، مؤكدةً أن القيمة المُعادة هي بالضبط ما كُتب. إن فشل المسبار بينما تبدو جميع SLIs بصحة جيدة، فلديك إخفاق بيزنطي جارٍ.