تصميم خدمة اختصار الروابط
التعليمات
الهدف
صمم ونفّذ خدمة اختصار روابط تتعامل مع 100 مليون رابط جديد شهريًا بنسبة قراءة إلى كتابة 10:1 واحتفاظ بالبيانات لمدة 5 سنوات. يختبر هذا المعمل قدرتك على دمج تصميم API وخوارزميات الترميز ونمذجة قواعد البيانات والتخزين المؤقت والتحليلات والتفكير في التوسع في نظام متماسك.
تقدير السعة
قبل كتابة أي كود، أكمل قسم تقدير السعة في الكود المبدئي. استخدم هذه المعطيات:
- الروابط الجديدة شهريًا: 100 مليون
- نسبة القراءة إلى الكتابة: 10:1
- متوسط حجم سجل الرابط: 500 بايت (الكود القصير + الرابط الطويل + البيانات الوصفية)
- فترة الاحتفاظ: 5 سنوات
- نسبة إصابة الكاش المستهدفة: 80% (توزيع باريتو — 20% من الروابط تحصل على 80% من حركة المرور)
يجب أن تحسب:
- QPS الكتابة (المتوسط والذروة عند 3 أضعاف)
- QPS القراءة (المتوسط والذروة عند 3 أضعاف)
- إجمالي التخزين المطلوب خلال 5 سنوات
- ذاكرة الكاش اليومية المطلوبة
- عرض النطاق اليومي للقراءات
المتطلبات
1. نقاط نهاية API
صمم واجهة RESTful بهذه النقاط:
POST /api/shorten— إنشاء رابط قصير من رابط طويل- جسم الطلب:
{ "long_url": "https://...", "custom_alias?": "my-link", "expires_at?": "2027-01-01" } - الاستجابة:
{ "short_url": "https://short.url/Ab3xK9z", "short_code": "Ab3xK9z", "expires_at": "2027-01-01" }
- جسم الطلب:
GET /:shortCode— إعادة التوجيه إلى الرابط الأصلي (HTTP 301 أو 302)GET /api/stats/:shortCode— الحصول على تحليلات لرابط قصير- الاستجابة:
{ "total_clicks": 1523, "clicks_by_day": [...], "top_referrers": [...], "top_countries": [...] }
- الاستجابة:
DELETE /api/urls/:shortCode— حذف رابط قصير (المالك فقط)
2. منطق ترميز Base62
نفّذ فئة Base62Encoder التي:
- تحوّل معرفًا رقميًا إلى سلسلة Base62 (الأحرف:
a-z،A-Z،0-9) - تحوّل سلسلة Base62 إلى معرف رقمي
- تُنشئ أكوادًا من 7 أحرف بالضبط (تُكمل بـ
aفي البداية إذا لزم الأمر) - تتعامل مع النطاق الكامل: 0 إلى 62^7 - 1 (3.5 تريليون)
3. مخطط قاعدة البيانات
صمم مخططًا يدعم:
- تخزين ربط الروابط مع بيانات وصفية (المنشئ، تاريخ الإنشاء، انتهاء الصلاحية)
- بحث فعال بالكود القصير (نمط الوصول الأساسي)
- تخزين أحداث التحليلات (طابع النقرة الزمني، IP، وكيل المستخدم، المُحيل، البلد)
- حالة تحديد المعدل لكل مفتاح API
قدم مخططات لـ كلا PostgreSQL (علائقي) و DynamoDB (NoSQL) حتى يتمكن المقيّم من تقييم فهمك لكلا النموذجين.
4. طبقة تخزين Redis المؤقت
نفّذ فئة CacheService التي:
- تستخدم نمط cache-aside لبحث الروابط
- تعيّن TTL بناءً على شعبية الرابط (الروابط الساخنة تحصل على TTL أطول)
- تتعامل مع إبطال الكاش عند حذف الرابط
- تتضمن تسخين الكاش للروابط المُنشأة حديثًا المتوقع أن تكون شائعة
5. تتبع التحليلات
نفّذ ClickTracker الذي:
- يسجل كل إعادة توجيه كحدث تحليلات (غير مانع، أطلق وانسَ)
- يستخرج الموقع الجغرافي من IP (ضع بديلاً وهميًا للبحث الجغرافي)
- يحلل وكيل المستخدم لمعلومات الجهاز/المتصفح
- يُجمّع الأحداث لكتابات قاعدة بيانات فعالة
- يوفر إحصائيات مُجمّعة (نقرات باليوم، أعلى المُحيلين، أعلى البلدان)
6. تحديد المعدل
نفّذ مُحدد معدل بدلو الرموز الذي:
- يحد إنشاء الروابط إلى 10 في الدقيقة لكل مفتاح API
- يحد إعادة التوجيه إلى 100 في الدقيقة لكل IP (منع سوء الاستخدام)
- يستخدم Redis للحالة الموزعة
- يرجع استجابة HTTP 429 مناسبة مع ترويسة
Retry-After
7. خطة التوسع الأفقي
في قسم ScalingPlan من تقديمك، صِف (كتعليقات كود أو سلسلة وثيقة تصميم):
- كيف ستُجزّئ قاعدة بيانات الروابط (أي مفتاح، أي استراتيجية، لماذا)
- كيف ستتعامل مع اتساق الكاش عبر مثيلات خادم API المتعددة
- كيف ستوسع خط أنابيب التحليلات للتعامل مع 10 أضعاف حركة المرور
- ما هي استراتيجية المراقبة والتنبيه (المقاييس الرئيسية)
- كيف ستتعامل مع مشكلة "القطيع المتهافت" للروابط الفيروسية
تلميحات
- لترميز Base62، فكر فيه كتحويل رقم إلى الأساس 62 (مشابه لتحويل العشري إلى الست عشري، لكن بـ 62 رقمًا بدلاً من 16)
- لمخطط قاعدة البيانات، اعتبر أن القراءات تفوق الكتابات بكثير — حسّن مسار القراءة أولاً
- للتحليلات، استخدم قائمة رسائل (Kafka) لفصل تسجيل النقرات عن مسار إعادة التوجيه — إعادة التوجيه يجب أن تكون سريعة
- لتحديد المعدل، استخدم سكربت Lua في Redis لجعل الفحص والإنقاص ذريين
- خطة التوسع يجب أن تعالج نقطة الاختناق المحددة في كل طبقة: خوادم API عديمة الحالة تتوسع بسهولة، لكن طبقات قاعدة البيانات والكاش تحتاج تجزئة دقيقة
ما يجب تقديمه
يجب أن يحتوي تقديمك على قسم ملف واحد في المحرر أدناه: ملف TypeScript كامل يحتوي على جميع الأقسام الثمانية.