الخدمات المصغّرة والطوابير
درس 3 من 3
3الخدمات المصغّرة والطوابير
الصورة الكبيرةالخدمات المصغّرة تبادل تعقيد الكود بتعقيد الشبكة: كل استدعاء قد يتأخر أو يفشل. الطوابير تفك الارتباط: الخدمة ترسل الرسالة وتكمل، والأخرى تعالجها حين تستطيع.
ماذا تشاهد في الرسم: التواصل غير المتزامن: المرسل لا ينتظر، والرسالة تبقى في الطابور حتى تُعالج بنجاح.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
مطعم صغير: الطاهي يأخذ الطلب ويطبخ ويغسل الصحون. حين يكبر، تفصل المطبخ عن الغسيل، وبينهما رف: الطاهي يضع الصحون على الرف ويكمل، والغاسل يأخذ منه حين يفرغ. الرف هو الطابور. لكن لا تفصل كل شخص في غرفة منذ اليوم الأول، فالتنسيق بين الغرف مكلف.
الخدمة المصغّرة تملك بياناتها وتُنشر مستقلة، وحدودها تتبع مجالات العمل (bounded contexts)، لا الطبقات التقنية. التكلفة: شبكة غير موثوقة، تتبع موزع، اتساق نهائي، ونشر أعقد. التواصل المتزامن (HTTP/gRPC) يحتاج مهلة وإعادة محاولة بتأخر أسي وcircuit breaker. غير المتزامن (RabbitMQ، SQS، Kafka) يضمن عادة «مرة واحدة على الأقل»، فالمستهلك يجب أن يكون idempotent (مفتاح فريد أو جدول رسائل معالجة). الرسائل التي تفشل مرارًا تذهب لطابور الرسائل الميتة (DLQ). ولتجنّب الكتابة في القاعدة دون نشر الحدث: نمط outbox.
قبل أن تبدأ
⚖️التوسع
درس التوسع وموازنة الأحمال.
⏳البرمجة غير المتزامنة
مسار الواجهات غير المتزامنة.
الفكرة الجوهرية
1monolith معياري أولًا
تطبيق واحد يُنشر مرة، لكن مقسّم داخليًا لوحدات بحدود واضحة (orders، payments، notifications) لا تلمس جداول بعضها مباشرة. حين تحتاج فعلًا لفصل وحدة، حدودها جاهزة.
2طابور مع إعادة محاولة
المستهلك يعالج، وعند الفشل تُعاد الرسالة للطابور حتى حد، ثم تذهب لطابور الرسائل الميتة:
from collections import deque
queue = deque([{"id": "m1", "order": 1}, {"id": "m2", "order": 2}, {"id": "m3", "order": 3}])
dead_letters, attempts = [], {}
flaky = {"m2": 1} # m2 fails once, then works
broken = {"m3"} # m3 always fails
MAX_ATTEMPTS = 3
def handle(msg):
if msg["id"] in broken:
raise RuntimeError("bad payload")
if flaky.get(msg["id"], 0) > 0:
flaky[msg["id"]] -= 1
raise TimeoutError("email service slow")
print("sent receipt for order", msg["order"])
while queue:
msg = queue.popleft()
attempts[msg["id"]] = attempts.get(msg["id"], 0) + 1
try:
handle(msg)
except Exception as e:
if attempts[msg["id"]] >= MAX_ATTEMPTS:
dead_letters.append((msg["id"], str(e)))
else:
queue.append(msg)
print("dead letters:", dead_letters)3idempotency: نفس الرسالة مرتين
الطابور يضمن «مرة على الأقل»: لو انقطع المستهلك بعد المعالجة وقبل التأكيد، تصل الرسالة مرة أخرى. لو كانت «اخصم 50 ريالًا» سيُخصم مرتين. الحل: سجّل معرّف كل رسالة عولجت وتجاهل المكرر:
balance = {"sara": 100}
processed = set()
def charge(msg):
if msg["id"] in processed:
print("duplicate", msg["id"], "skipped")
return
balance[msg["user"]] -= msg["amount"]
processed.add(msg["id"])
payment = {"id": "pay-7f3", "user": "sara", "amount": 50}
charge(payment)
charge(payment) # redelivered after a crash
print("balance:", balance["sara"])مثال محلول
المسألة: إتمام الطلب يستدعي خدمة البريد مباشرة، والبريد بطيء أحيانًا 8 ثوانٍ، فيرى المستخدم دائرة تحميل وأحيانًا خطأ رغم نجاح الدفع. الحل: الطلب يُحفظ وينشر حدث order.paid في الطابور ويرد فورًا. مستهلك البريد يعالج الحدث بإعادة محاولة، وبمعرّف الطلب كمفتاح idempotency حتى لا يصل بريدان. النتيجة: إتمام الطلب في 200ms، والبريد يصل ولو تأخر.
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| الخدمات المصغّرة أسرع. | كل استدعاء شبكة أبطأ من استدعاء دالة. فائدتها استقلال الفرق والنشر والتوسع. |
| الطابور يضمن وصول الرسالة مرة واحدة بالضبط. | أغلبها «مرة على الأقل»، والمسؤولية على المستهلك. |
أين تقابله في العمل
📧المهام الخلفية
البريد والتقارير والصور خارج مسار الطلب.
📦الأحداث بين الفرق
فريق المخزون يستمع لأحداث الطلبات دون استدعاء مباشر.
اختبر فهمك
متى تقسّم النظام لخدمات؟
التقسيم يحل مشاكل تنظيمية وتوسعية، ويكلّف تعقيدًا.
لماذا يجب أن يكون المستهلك idempotent؟
«مرة على الأقل» تعني احتمال التكرار.
رسالة تفشل دائمًا. أين تذهب؟
DLQ يحفظها للفحص دون أن تسد الطابور.
بطاقات مراجعة سريعة
تحقق من فهمك
ما «الـ monolith الموزع»؟
خدمات منفصلة شكليًا لكنها تتشارك القاعدة وتُنشر معًا، فتجمع عيوب الطريقتين.
ماذا تضيف للاستدعاء المتزامن بين الخدمات؟
مهلة، وإعادة محاولة بتأخر أسي، وcircuit breaker.
ما مشكلة الحفظ ثم النشر في خطوتين؟
لو انهار بينهما، تُحفظ البيانات ولا يُنشر الحدث. نمط outbox يحلها.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.