منهجية تصميم الأنظمة
مقدمة
تصميم الأنظمة ليس رسم مخططات بنية عشوائيًا، بل منهجية منظمة. سواء كان سؤال تصميم أنظمة في مقابلة أو تصميم بنية في العمل الفعلي، كلاهما يتبع إطار تفكير مشابه: أفهم المشكلة أولًا، ثم أُقدّر الحجم، ثم أصمم الحل، وأخيرًا أُعمّق التحسين.
ماذا ستتعلم من هذه المقالة؟
بعد إتمام هذا الفصل، ستكتسب:
- عملية التصميم: إتقان إطار الخطوات الأربع لتصميم الأنظمة
- تقدير السعة: تعلم تقنية "التقدير على ظهر الظرف"
- الأنماط الشائعة: الإلمام بالأنماط الأساسية مثل التخزين المؤقت، وتقسيم قواعد البيانات والجداول، وقوائم انتظار الرسائل
- التفكير بالمفاضلات: فهم التفكير بالمفاضلات (trade-off) في تصميم البنى
- حالات عملية: فهم عملية التصميم من خلال حالات مثل خدمة الروابط القصيرة، وتدفقات المحتوى
| الفصل | المحتوى | المفاهيم الأساسية |
|---|---|---|
| الفصل 1 | طريقة الخطوات الأربع | توضيح المتطلبات، تقدير السعة، تصميم البنية، التعميق والتحسين |
| الفصل 2 | تقدير السعة | QPS، التخزين، عرض النطاق، التقدير على ظهر الظرف |
| الفصل 3 | أنماط التصميم الأساسية | التخزين المؤقت، تقسيم قواعد البيانات والجداول، قوائم انتظار الرسائل، CDN |
| الفصل 4 | التفكير بالمفاضلات | التناسق مقابل التوافر، الأداء مقابل التكلفة |
| الفصل 5 | حالات كلاسيكية | خدمة الروابط القصيرة، تدفقات المحتوى، نظام البيع المباشر |
1. طريقة الخطوات الأربع لتصميم الأنظمة
تصميم الأنظمة لا يبدأ برسم مخطط البنية مباشرة. سواء في المقابلة أو في الممارسة الفعلية، يجب اتباع عملية منظمة.
لماذا توضيح المتطلبات أولًا؟
الكثيرون يبدأون بالرسم فورًا، ليصمموا نظامًا "صحيحًا لكن ليس ما يريده المحاور". خصص 5 دقائق لسؤال المتطلعات بوضوح، وتجنب 30 دقيقة من إعادة العمل.
أسئلة التوضيح الشائعة:
- ما هي الوظيفة الأساسية للنظام؟ (لا تصمم كل الوظائف)
- ما حجم المستخدمين؟ (يُحدد الحاجة للتوزيع)
- ما نسبة القراءة إلى الكتابة؟ (تُحدد استراتيجية التخزين المؤقت)
- ما المدة التي يجب الاحتفاظ بالبيانات فيها؟ (تُحدد حل التخزين)
2. تقدير السعة: فن التقدير على ظهر الظرف
"التقدير على ظهر الظرف" (Back-of-envelope estimation) مهارة أساسية في تصميم الأنظمة. لا يحتاج حسابًا دقيقًا، بل معرفة الحجم التقريبي فقط.
جدول مرجعي سريع للتحويلات الشائعة
| الحجم | التحويل | طريقة التذكر |
|---|---|---|
| يوم واحد | 86,400 ثانية | ≈ 100 ألف ثانية |
| 100 مليون طلب/يوم | ≈ 1,200 QPS | القسمة على 100 ألف |
| 1 كيلوبايت × 100 مليون | ≈ 100 جيجابايت | 100 مليون سجل صغير |
| 1 ميجابايت × مليون | ≈ 1 تيرابايت | مليون صورة |
تطبيق قاعدة 80/20 في التقدير
معظم الأنظمة تتبع قاعدة 80/20: 20% من البيانات تستحوذ على 80% من الطلبات. هذا يعني:
- حجم التخزين المؤقت ≈ إجمالي البيانات × 20%
- QPS النقاط الساخنة ≈ إجمالي QPS × 80% متركزة على 20% من المفاتيح
- معدل إصابة التخزين المؤقت المستهدف ≈ 80%+ (أقل من هذا يعني مشكلة في استراتيجية التخزين المؤقت)
3. أنماط التصميم الأساسية
أنماط تتكرر في تصميم الأنظمة، وإتقانها يمكّنك من التعامل مع معظم السيناريوهات.
3.1 أنماط التخزين المؤقت
| النمط | مسار القراءة | مسار الكتابة | السيناريو المناسب |
|---|---|---|---|
| Cache-Aside | استعلم التخزين المؤقت أولًا، عند الفقدان استعلم قاعدة البيانات وأعد التعبئة | اكتب قاعدة البيانات أولًا، ثم احذف التخزين المؤقت | الاستخدام العام، الأكثر شيوعًا |
| Read-Through | طبقة التخزين المؤقت تحمّل تلقائيًا من قاعدة البيانات | مثل Cache-Aside | يحتاج دعم إطار التخزين المؤقت |
| Write-Behind | مثل Cache-Aside | اكتب التخزين المؤقت أولًا، واكتب قاعدة البيانات بشكل غير متزامن | عمليات كتابة كثيفة، يمكن تحمل فقدان بعض البيانات |
لماذا "حذف التخزين المؤقت" بدلاً من "تحديث التخزين المؤقت"؟
تحديث التخزين المؤقت في سيناريوهات التزامن عرضة لعدم تناسق البيانات: الخيطان A و B يحدّثان في نفس الوقت، A يكتب قاعدة البيانات أولًا لكن B يحدّث التخزين المؤقت أولًا، مما يجعل التخزين المؤقت يحتوي على القيمة القديمة لـ B. حذف التخزين المؤقت يجعل طلب القراءة التالي يعيد التحميل من قاعدة البيانات، مما يتجنب هذه المشكلة بشكل طبيعي.
3.2 تقسيم قواعد البيانات والجداول
عندما يتجاوز حجم البيانات في جدول واحد عشرات الملايين، أو يتجاوز QPS لقاعدة بيانات واحدة عنق الزجاجة، يجب التفكير في تقسيم قواعد البيانات والجداول.
| الاستراتيجية | الطريقة | المزايا | العيوب |
|---|---|---|---|
| التقسيم العمودي لقواعد البيانات | تقسيم قواعد البيانات حسب النطاق التجاري | فك الارتباط التجاري، توسع مستقل | صعوبة JOIN عبر قواعد البيانات |
| التقسيم الأفقي للجداول | تقسيم الجدول نفسه لعدة جداول وفقًا لقواعد | حجم بيانات الجدول الواحد مُتحكم | اختيار مفتاح التقسيم أمر حاسم |
| التقسيم العمودي للجداول | نقل الحقول الكبيرة لجدول مستقل | تقليل IO، تحسين كفاءة الاستعلام | يحتاج JOIN إضافي |
مبادئ اختيار مفتاح التقسيم:
- اختر الحقل الأكثر استخدامًا في الاستعلامات (مثل user_id)
- يجب أن يكون توزيع البيانات متساويًا، لتجنب النقاط الساخنة
- حاول أن تكون بيانات نفس المستخدم في نفس القسم (لتقليل الاستعلامات عبر الأقسام)
3.3 قوائم انتظار الرسائل
قوائم انتظار الرسائل هي "ممتص الصدمات" في الأنظمة الموزعة، ودورها الأساسي هو الفصل، وعدم التزامن، وتسوية الذروة.
| السيناريو | بدون قائمة انتظار | مع قائمة انتظار |
|---|---|---|
| إرسال إشعار بعد الطلب | واجهة الطلب تستدعي خدمة الإشعار بشكل متزامن، فشل الإشعار يُفشل الطلب | بعد نجاح الطلب يُرسل رسالة، خدمة الإشعار تستهلك بشكل غير متزامن |
| بيع مباشر | حركة لحظية تُعطب قاعدة البيانات | الطلبات تدخل القائمة أولًا، الخلفية تستهلك بحسب القدرة |
| مزامنة البيانات | الخدمة A تستدعي واجهة الخدمة B مباشرة | الخدمة A تنشر حدثًا، الخدمة B تشترك وتعالج |
4. التفكير بالمفاضلات: لا توجد رصاصة فضية
جوهر تصميم البنى هو المفاضلة (Trade-off). كل قرار له ثمن، والمفتاح هو فهم الثمن واتخاذ الخيار المناسب للمرحلة الحالية.
| بُعد المفاضلة | الخيار A | الخيار B | معيار القرار |
|---|---|---|---|
| التناسق مقابل التوافر | تناسق قوي (CP) | توافر عالٍ (AP) | هل يمكن للعمل تحمل عدم تناسق مؤقت؟ |
| الأداء مقابل التكلفة | تخزين مؤقت كامل | تخزين مؤقت عند الحاجة | حجم البيانات والميزانية |
| البساطة مقابل المرونة | بنية متجانبة | خدمات مصغرة | حجم الفريق وتعقيد العمل |
| الوقت الفعلي مقابل الدفعات | معالجة تدفقية | معالجة دفعات | متطلبات حداثة البيانات |
| بناء ذاتي مقابل مُدار | بناء MySQL الخاصة | استخدام RDS السحابية | قدرات التشغيل والتكلفة |
سجل قرارات البنية (ADR)
كل قرار بنية مهم يجب توثيقه: ما الخلفية، ما الخيارات المدروسة، لماذا اُختير هذا، ما الثمن. هذا ليس للتهرب من المسؤولية، بل ليفهم من يأتي بعدنا "لماذا صُمم هكذا آنذاك".
التنسيق بسيط:
- العنوان: استخدام XXX بدلاً من YYY
- الخلفية: ما المشكلة التي واجهناها
- القرار: ما الحل الذي اخترناه
- السبب: لماذا اخترنا هذا
- الثمن: عيوب ومخاطر هذا القرار
المفاضلات الخاطئة الشائعة
| الخطأ | المظهر | الطريقة الصحيحة |
|---|---|---|
| التحسين المبكر | 1000 مستخدم يومي وتُقسّم قواعد البيانات والجداول | استخدم قاعدة بيانات واحدة أولًا، وقسّم عند الوصول للعنق |
| القيادة التقنية | "أريد استخدام Kafka" بدلاً من "أحتاج عدم تزامن" | انطلق من المشكلة، وليس من التكنولوجيا |
| تجاهل تكلفة التشغيل | اُختير الحل الأمثل لكن الفريق لا يستطيع صيانته | يجب أن يتناسب الحل مع قدرات الفريق |
| السعي للتناسق المثالي | استخدام المعاملات الموزعة في جميع السيناريوهات | التناسق النهائي يكفي في معظم السيناريوهات |
5. حالات كلاسيكية
من خلال ثلاث حالات كلاسيكية، نربط المنهجية التي تعلمناها.
5.1 خدمة الروابط القصيرة (TinyURL)
خدمة الروابط القصيرة سؤال كلاسيكي في مقابلات تصميم الأنظمة، صغيرة لكنها متكاملة.
توضيح المتطلبات:
- الوظيفة الأساسية: رابط طويل ← رابط قصير (كتابة)، رابط قصير ← إعادة توجيه (قراءة)
- نسبة القراءة إلى الكتابة: حوالي 100:1 (القراءة أكثر بكثير من الكتابة)
- إعادة التوجيه اليومية: 100 مليون
- الروابط القصيرة لا تنتهي صلاحيتها أبدًا
تقدير السعة:
| المؤشر | الحساب | النتيجة |
|---|---|---|
| كتابة QPS | 100 مليون / 100 / 86400 | ≈ 12 QPS |
| قراءة QPS | 100 مليون / 86400 | ≈ 1,200 QPS |
| ذروة قراءة QPS | 1,200 × 3 | ≈ 3,600 QPS |
| تخزين 5 سنوات | مليون/يوم × 365 × 5 × 100B | ≈ 18 GB |
| تخزين مؤقت (20%) | 18 GB × 20% | ≈ 3.6 GB |
تصميم البنية:
مسار الكتابة: العميل ← API Server ← مُولّد المعرفات ← ترميز Base62 ← كتابة MySQL + Redis
مسار القراءة: العميل ← CDN ← API Server ← استعلام Redis ← إعادة توجيه 302
↓ (فقدان التخزين المؤقت)
استعلام MySQL ← إعادة تعبئة Redisقرارات التصميم الرئيسية:
- توليد الرمز القصير: معرف موزع Snowflake + ترميز Base62، لتجنب تصادم التجزئة
- استراتيجية التخزين المؤقت: Cache-Aside، تسريع الروابط القصيرة الساخنة عبر CDN
- قاعدة البيانات: جدول واحد يكفي (18GB صغير)، فهرسة حسب الرمز القصير
5.2 نظام تدفق المحتوى (Feed)
تدفق المحتوى في منصات التواصل الاجتماعي (لحظات WeChat، الصفحة الرئيسية لـ Weibo) سؤال كلاسيكي آخر.
التحدي الأساسي: نشر المستخدم محتوى، كيف يراه جميع المتابعين؟
| الحل | الطريقة | المزايا | العيوب |
|---|---|---|---|
| نمط السحب (Pull) | التجميع الفوري لمحتوى المتابعين عند القراءة | كتابة بسيطة، تخزين أقل | قراءة بطيئة، تأخر عالٍ عند كثرة المتابعين |
| نمط الدفع (Push) | الكتابة لصناديق وارد جميع المتابعين عند النشر | قراءة سريعة جدًا | انتشار كتابة كبير عند المؤثرين |
| الدفع والسحب معًا | المستخدمون العاديون بالدفع، المؤثرون بالسحب | توازن بين أداء القراءة والكتابة | تنفيذ معقد |
حل الدفع والسحب معًا:
- المتابعون < 10 آلاف: عند النشر يُدفع لذاكرة التخزين المؤقت لتدفق جميع المتابعين (نمط الدفع)
- المتابعون > 10 آلاف: لا يُدفع، يسحب المتابعون في الوقت الفعلي عند القراءة (نمط السحب)
- عند فتح المستخدم للتدفق: يُدمج المحتوى المدفوع + المحتوى المسحوب فوريًا من المؤثرين، ويُرتب زمنيًا
5.3 نظام البيع المباشر (Flash Sale)
التحدي الأساسي في البيع المباشر: تزامن فائق في لحظة واحدة + عدم تجاوز المخزون.
خصائص الحركة:
- قبل بدء النشاط: عدد كبير من المستخدمين يُحدّثون الصفحة انتظارًا
- لحظة بدء النشاط: QPS قد يكون 100 ضعف المعدل الطبيعي أو أكثر
- بعد انتهاء النشاط: الحركة تنخفض بسرعة
استراتيجية تسوية الذروة متعددة الطبقات:
طلب المستخدم ← CDN (صفحات ثابتة) ← البوابة (تحديد المعدل) ← قائمة انتظار الرسائل (تسوية الذروة) ← خدمة المخزون (خصم)| الطبقة | الاستراتيجية | التأثير |
|---|---|---|
| الواجهة الأمامية | تعطيل الزر + تأخير عشوائي + رمز تحقق | تصفية الروبوتات، تشتيت الطلبات |
| CDN | تخزين مؤقت للموارد الثابتة | تقليل 90% من طلبات الصفحة |
| البوابة | تحديد معدل بدلو الرموز | تمرير فقط ما يمكن للنظام تحمله |
| قائمة انتظار الرسائل | دخول الطلبات في القائمة، معالجة غير متزامنة | تسوية الذروة، حماية قاعدة البيانات |
| خدمة المخزون | خصم مسبق عبر Redis + عملية Lua ذرية | منع تجاوز المخزون، استجابة بالملي ثانية |
المبادئ الأساسية للبيع المباشر
- اعترض في أعلى مستوى ممكن: ما يمكن لـ CDN إيقافه لا تدعه يصل لطبقة التطبيق
- فصل القراءة والكتابة: صفحة تفاصيل المنتج عبر التخزين المؤقت، والطلب فقط عبر قاعدة البيانات
- المعالجة غير المتزامنة: بعد نقر المستخدم "شراء" يُعاد "في قائمة الانتظار" فورًا، والمعالجة في الخلفية بشكل غير متزامن
- خطة احتياطية: تحديد المعدل، قواطع الدائرة، تقليل الخدمة — لكل طبقة خطة بديلة عند أي مشكلة
الخلاصة
تصميم الأنظمة مهارة عملية بامتياز، وجوهرها في التفكير المنظم والمفاضلات.
نراجع النقاط الرئيسية في هذا الفصل:
- إطار الخطوات الأربع: توضيح المتطلبات ← تقدير السعة ← تصميم البنية ← التعميق والتحسين، لا يمكن تخطي أي خطوة
- التقدير على ظهر الظرف: لا يحتاج دقة، بل معرفة الحجم التقريبي لتوجيه قرارات البنية
- الأنماط الأساسية: التخزين المؤقت، تقسيم قواعد البيانات والجداول، قوائم انتظار الرسائل، CDN، تحديد المعدل وقواطع الدائرة — هذه "لبنات" تصميم الأنظمة
- التفكير بالمفاضلات: لا يوجد حل مثالي، بل حل مناسب للمرحلة الحالية، وسجّل سبب وثمن كل قرار
- الحالات الكلاسيكية: خدمة الروابط القصيرة للأساسيات، تدفق المحتوى لنموذج الدفع والسحب، البيع المباشر للتزامن العالي — إتقان هذه الثلاثة يُمكّنك من التطبيق على غيرها
قراءات إضافية
- System Design Interview - كتاب أليكس شو الكلاسيكي لمقابلات تصميم الأنظمة
- Designing Data-Intensive Applications - كتاب مارتن كليبمان لتصميم التطبيقات كثيفة البيانات
- The System Design Primer - أكثر موارد تعلم تصميم الأنظمة شمولًا على GitHub
- ByteByteGo - مدونة أليكس شو المرئية لتصميم الأنظمة