الأنظمة الموزعة والموثوقية
الموثوقية والمراقبة
النظام الخلفي الموزع الذي لا يستطيع قياس صحته يطير أعمى. يغطي هذا الدرس مجموعة أدوات SRE التي يتوقع المحاورون من المرشحين الأوائل معرفتها: SLOs وقواطع الدوائر واستراتيجيات إعادة المحاولة والتتبع الموزع وهندسة الفوضى.
SLOs و SLIs و SLAs
هذه المصطلحات الثلاثة تشكل تسلسلاً هرميًا من القياس إلى الوعد:
| المصطلح | ما هو | مثال |
|---|---|---|
| SLI (مؤشر مستوى الخدمة) | مقياس مُقاس | زمن استجابة p99 = 187 مللي ثانية |
| SLO (هدف مستوى الخدمة) | هدف للمؤشر SLI | 99.9% من الطلبات < 200 مللي ثانية |
| SLA (اتفاقية مستوى الخدمة) | التزام تعاقدي مع عقوبات | "إذا انخفض وقت التشغيل عن 99.95%، يحصل العميل على رصيد 10%" |
جدول مستويات التوفر
| الهدف | التوقف السنوي | التوقف الشهري | الاستخدام النموذجي |
|---|---|---|---|
| 99% (رقمان) | 3.65 أيام | 7.3 ساعات | أدوات داخلية، مهام دُفعية |
| 99.9% (ثلاثة أرقام) | 8.76 ساعات | 43.8 دقيقة | معظم منتجات SaaS |
| 99.99% (أربعة أرقام) | 52.6 دقيقة | 4.38 دقائق | أنظمة الدفع، واجهات API أساسية |
| 99.999% (خمسة أرقام) | 5.26 دقائق | 26.3 ثانية | DNS، حافة CDN، أجهزة تنظيم ضربات القلب |
ميزانيات الخطأ
ميزانية الخطأ هي مقدار عدم الموثوقية المسموح به: ميزانية الخطأ = 1 - SLO.
مع SLO توفر 99.9%:
- ميزانية الخطأ = 0.1% = 8.76 ساعات/سنة من التوقف
- إذا استهلكت 6 ساعات في الربع الأول من حادثة، يتبقى 2.76 ساعة
- إذا استُنفدت الميزانية، جمّد عمليات النشر وركّز على الموثوقية
نصيحة للمقابلة: ميزانيات الخطأ توحّد الحوافز — فرق المنتج تريد الإطلاق بسرعة، وفرق SRE تريد الاستقرار. ميزانية الخطأ تعطي كلا الطرفين رقمًا مشتركًا للتفاوض حوله.
نمط قاطع الدائرة (Circuit Breaker)
قاطع الدائرة يمنع الإخفاقات المتتالية عبر الفشل السريع للطلبات إلى خدمة غير صحية بدلاً من تركها تنتهي مهلتها وتستنفد الموارد.
آلة الحالة
┌──────────────────────────────────────────────────┐
│ │
│ ┌─────────┐ الإخفاقات >= الحد ┌──────────┐ │
│ │ مُغلق │ ─────────────────────► │ مفتوح │ │
│ │(عادي) │ │(فشل │ │
│ │ │ ◄───────────────────── │ سريع) │ │
│ └─────────┘ النجاحات >= الحد └──┬──────┘ │
│ ▲ │ │
│ │ │ │
│ │ ┌───────────┐ مهلة │ │
│ │ │ نصف مفتوح │ ◄───────────┘ │
│ │ │ (اختبار) │ │
│ │ └─────┬─────┘ │
│ │ │ │
│ │ نجاح │ فشل │
│ └──────────────┘──────────────► مفتوح │
└──────────────────────────────────────────────────┘
الحالات:
- مُغلق (عادي): الطلبات تمر. عدّ الإخفاقات. إذا وصلت الإخفاقات للحد (مثلاً 5 إخفاقات في 60 ثانية)، انتقل إلى مفتوح.
- مفتوح (فشل سريع): جميع الطلبات تُرجع خطأ أو استجابة احتياطية فورًا. بدون استدعاءات للخدمة. بعد فترة مهلة (مثلاً 30 ثانية)، انتقل إلى نصف مفتوح.
- نصف مفتوح (اختبار): اسمح لعدد محدود من الطلبات بالمرور. إذا نجحت، انتقل إلى مُغلق. إذا فشلت أي منها، عُد إلى مفتوح.
# شبه كود: منطق قاطع الدائرة
class CircuitBreaker:
def __init__(self):
self.state = "CLOSED"
self.failure_count = 0
self.failure_threshold = 5
self.timeout_duration = 30 # ثواني
self.success_threshold = 3
self.last_failure_time = None
def call(self, func):
if self.state == "OPEN":
if time_since(self.last_failure_time) > self.timeout_duration:
self.state = "HALF_OPEN"
else:
raise CircuitOpenError("الخدمة غير متوفرة")
try:
result = func()
self.on_success()
return result
except Exception as e:
self.on_failure()
raise e
def on_success(self):
if self.state == "HALF_OPEN":
self.success_count += 1
if self.success_count >= self.success_threshold:
self.state = "CLOSED"
self.failure_count = 0
def on_failure(self):
self.failure_count += 1
self.last_failure_time = now()
if self.failure_count >= self.failure_threshold:
self.state = "OPEN"
المكتبات: resilience4j (Java)، gobreaker (Go)، pybreaker (Python). Hystrix (Netflix) في وضع الصيانة — استخدم resilience4j بدلاً منها.
استراتيجيات إعادة المحاولة
التراجع الأسي مع الارتعاش (Jitter)
إعادة المحاولة البسيطة تسبب القطيع المتدافع — آلاف العملاء يعيدون المحاولة في وقت واحد، مما يُغرق الخدمة المتعافية.
التأخير = الأساس * 2^المحاولة
المحاولة 0: 1 ثانية
المحاولة 1: 2 ثانية
المحاولة 2: 4 ثوانٍ
المحاولة 3: 8 ثوانٍ
المحاولة 4: 16 ثانية (محدود بالحد الأقصى)
الارتعاش الكامل يُعشوئ التأخير لتوزيع المحاولات عبر الوقت:
# التراجع الأسي مع الارتعاش الكامل
import random
def retry_with_backoff(func, max_retries=4, base_delay=1.0, max_delay=30.0):
for attempt in range(max_retries + 1):
try:
return func()
except RetryableError:
if attempt == max_retries:
raise
exp_delay = min(base_delay * (2 ** attempt), max_delay)
jittered_delay = random.uniform(0, exp_delay)
time.sleep(jittered_delay)
تكامل إعادة المحاولة + قاطع الدائرة
- إعادة المحاولة تتعامل مع الإخفاقات العابرة (انقطاعات شبكة، أحمال مؤقتة)
- قواطع الدوائر تتعامل مع الإخفاقات المستمرة (الخدمة معطلة)
- معًا: أعد المحاولة 2-3 مرات، ثم إذا فُتحت الدائرة، توقف عن إعادة المحاولة فورًا
نصيحة للمقابلة: اذكر دائمًا الاثنين معًا. إعادة المحاولة بدون قاطع دائرة ستقصف خدمة فاشلة. قاطع دائرة بدون إعادة محاولة يستسلم بسهولة مع الأخطاء العابرة.
فحوصات الصحة
في تنسيق الحاويات (Kubernetes)، ثلاثة أنواع من المسابر تضمن وصول حركة المرور فقط إلى الحجرات الصحية:
| المسبار | السؤال | الإجراء عند الفشل |
|---|---|---|
| الحيوية (Liveness) | هل العملية حية؟ | اقتل وأعد تشغيل الحاوية |
| الجاهزية (Readiness) | هل يمكنها خدمة حركة المرور؟ | أزلها من موازن الحمل، توقف عن إرسال الطلبات |
| البدء (Startup) | هل انتهت من التهيئة؟ | لا تشغّل مسابر الحيوية/الجاهزية حتى ينجح البدء |
# تكوين فحوصات صحة Kubernetes
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
failureThreshold: 3 # أعد التشغيل بعد 3 إخفاقات متتالية
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 2 # أزل من الخدمة بعد إخفاقتين
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # اسمح بحتى 5 دقائق للبدء (30 * 10 ثوانٍ)
periodSeconds: 10
قرارات التصميم الرئيسية:
- الحيوية: تحقق فقط من صحة العملية (هل تستجيب لـ HTTP، ليست في حالة جمود). لا تتحقق من الاعتمادات — إذا كانت قاعدة البيانات معطلة، إعادة تشغيل التطبيق لن تصلحها.
- الجاهزية: تحقق من الاعتمادات (اتصال قاعدة البيانات، دفء الذاكرة المؤقتة، تحميل الإعدادات). عندما لا تكون جاهزة، تبقى الحجرة تعمل لكن تتوقف عن استقبال حركة المرور.
التتبع الموزع (OpenTelemetry)
التتبع يتابع طلبًا واحدًا عبر عدة خدمات:
معرف التتبع: abc-123
الخدمة A ─────────────────────────────────────────
│ Span: HTTP GET /orders (200 مللي ثانية إجمالاً) │
│ │
│ الخدمة B ────────────────────────── │
│ │ Span: استعلام DB (50 مللي ثانية) │ │
│ └──────────────────────────────────┘ │
│ │
│ الخدمة C ──────────────────────────────── │
│ │ Span: بحث Cache (5 مللي ثانية) │ │
│ │ Span: استدعاء API خارجي (120 مللي ثانية) │ │
│ └────────────────────────────────────────┘ │
└───────────────────────────────────────────────────┘
المفاهيم الأساسية:
- التتبع (Trace): الرحلة الكاملة للطلب (معرف تتبع واحد)
- المدى (Span): وحدة عمل واحدة ضمن التتبع (لها span_id و parent_span_id والمدة والحالة)
- سياق المدى (SpanContext): بيانات وصفية تُنقل عبر الخدمات (trace_id, span_id, trace_flags)
نقل السياق: SpanContext يُحقن في رؤوس HTTP (مثلاً traceparent: 00-abc123-def456-01) حتى تتمكن الخدمات اللاحقة من متابعة التتبع.
أدوات التصوير: Jaeger, Zipkin, Grafana Tempo
التسجيل المنظم (Structured Logging)
السجلات غير المنظمة ("خطأ في معالجة الطلب 123") شبه مستحيلة الاستعلام على نطاق واسع. التسجيل المنظم يُصدر JSON يمكن فهرسته والبحث فيه.
{
"timestamp": "2026-02-12T10:30:00Z",
"level": "ERROR",
"service": "order-service",
"trace_id": "abc-123",
"span_id": "def-456",
"correlation_id": "req-789",
"message": "فشل في معالجة الطلب",
"order_id": "ORD-123",
"error": "مهلة بوابة الدفع",
"duration_ms": 5023
}
الحقول الرئيسية: trace_id (يربط بالتتبع الموزع)، correlation_id (يربط بمعاملة الأعمال)، level (DEBUG, INFO, WARN, ERROR).
مقاييس طريقة RED
طريقة RED تعطيك ثلاثة مقاييس تغطي 90% من مراقبة الخدمات:
| المقياس | ماذا يقيس | حد التنبيه (مثال) |
|---|---|---|
| المعدل (Rate) | الطلبات في الثانية | انخفاض > 30% من خط الأساس |
| الأخطاء (Errors) | معدل الأخطاء (4xx + 5xx) | > 1% من إجمالي الطلبات |
| المدة (Duration) | توزيع زمن الاستجابة | p99 > 500 مللي ثانية |
المعدل: ████████████████████ 2,400 طلب/ثانية
الأخطاء: ██ 0.3% (جيد)
المدة: p50=12ms p95=89ms p99=210ms (جيد)
مبدأ التنبيه: نبّه على الأعراض (زمن استجابة عالٍ، معدل أخطاء مرتفع)، وليس الأسباب (استخدام CPU عالٍ). استخدام CPU عالٍ مقبول إذا كان زمن الاستجابة طبيعيًا. استخدام CPU منخفض سيء إذا كانت الخدمة في حالة جمود.
هندسة الفوضى (Chaos Engineering)
هندسة الفوضى تحقن الإخفاقات بشكل استباقي لإيجاد نقاط الضعف قبل أن تسبب انقطاعات.
المبادئ
- حدد الحالة المستقرة: قِس السلوك الطبيعي (الإنتاجية، معدل الأخطاء، زمن الاستجابة)
- ضع فرضية: "إذا أوقفنا نسخة قاعدة بيانات واحدة، سيتم التبديل خلال 30 ثانية بدون أخطاء"
- احقن الإخفاق: أوقف النسخة في الإنتاج (أو بيئة الاختبار)
- راقب: هل تصرف النظام كما افترضنا؟
- قلل نطاق الضرر: ابدأ صغيرًا (نسخة واحدة)، وسّع تدريجيًا
التجارب الشائعة
| التجربة | ماذا تختبر |
|---|---|
| اقتل حجرة عشوائية | إعادة التشغيل التلقائي، موازنة الحمل |
| أضف 500 مللي ثانية تأخير شبكة | إعدادات المهلة، قواطع الدوائر |
| املأ القرص إلى 95% | تدوير السجلات، التنبيهات |
| احجب الوصول لاعتمادية | السلوك الاحتياطي، التدهور الرشيق |
| احقن انحراف الساعة | المنطق المعتمد على الوقت، التحقق من الشهادات |
الأدوات: Netflix Chaos Monkey (إيقاف عشوائي للنسخ)، Gremlin (منصة فوضى تجارية)، Litmus (فوضى أصلية لـ Kubernetes)، tc (التحكم بحركة مرور Linux لتجارب الشبكة).
إطار المقابلة: عند مناقشة الموثوقية، امشِ خلال: "نضع SLOs، نقيس بـ SLIs، نستخدم ميزانيات الخطأ لموازنة السرعة والموثوقية، نحمي الخدمات بقواطع الدوائر وإعادة المحاولة، نراقب بالتتبعات والمقاييس، ونتحقق من افتراضاتنا بهندسة الفوضى."
لقد أكملت جميع الدروس الثلاثة في هذه الوحدة. أجرِ الاختبار لاختبار معرفتك بالأنظمة الموزعة والتزامن وهندسة الموثوقية. :::