تصميم نظام اختصار الروابط
تصميم نظام اختصار الروابط
يقوم نظام اختصار الروابط بتحويل رابط طويل مثل https://www.example.com/products/shoes/mens/running?color=blue&size=10&ref=homepage_banner إلى شيء مثل https://tinyurl.com/ab3x9k. تبدو المسألة بسيطة للوهلة الأولى، لكنها تُعدّ من أفضل مسائل المقابلات الهندسية لأنها تمسّ التشفير والتخزين وإعادة التوجيه والتوسّع في آنٍ واحد.
الخطوة 1 — تحديد المتطلبات
قبل أي تصميم، حدّد الأرقام بدقة:
- معدل الكتابة: 100 مليون رابط جديد يومياً (~1,160 كتابة/ثانية).
- معدل القراءة: نسبة 10:1 بين القراءة والكتابة → ~11,600 إعادة توجيه/ثانية.
- مدة حياة الرابط: 10 سنوات افتراضياً مع إمكانية التحديد.
- طول الكود القصير: 6–8 أحرف، فريد عالمياً.
- تقدير التخزين: 100 مليون رابط × 365 × 10 سنوات = 365 مليار صف. بحجم ~500 بايت لكل صف، يصل المجموع إلى ~180 تيرابايت خلال 10 سنوات.
الخطوة 2 — اختيار استراتيجية توليد الكود القصير
السؤال الجوهري: كيف تولّد كوداً قصيراً وفريداً وخالياً من التعارضات؟
الخيار A — ترميز Base-62 لعدّاد تسلسلي
احتفظ بعدّاد تزايدي عالمي. رمّز قيمته بنظام الأساس 62 (أرقام 0–9، أحرف a–z، A–Z). سلسلة Base-62 من 7 أحرف تتيح 627 ≈ 3.5 تريليون رابط فريد — أكثر من كافٍ لـ10 سنوات.
المشكلة: عدّاد وحيد يُشكّل نقطة اختناق. تحتاج خدمة عدّاد مخصصة (مثل INCR في Redis أو عدّاد موزع من نوع Snowflake). إذا تعطّلت عقدة العدّاد، توقّفت الكتابات.
الخيار B — تجزئة MD5/SHA-256 مع الاقتطاع
جزّئ الرابط الطويل وخذ أول 7 أحرف. بسيط جداً لكن احتمال التعارض غير معدوم؛ إذ قد يُنتج رابطان مختلفان نفس البادئة السبعية. يستلزم ذلك إعادة التجزئة بملح مختلف أو فحصاً إضافياً للتفرّد.
الخيار C — Base-62 عشوائي مع فحص قاعدة البيانات
أنشئ رمزاً عشوائياً من 7 أحرف Base-62، ثم حاول إدراجه. في حال التعارض، أعد المحاولة. احتمال التعارض عند 365 مليار صف لا يتجاوز 0.01 %، وهو مقبول عملياً.
الخطوة 3 — مخطط قاعدة البيانات
يضمن UNIQUE KEY على short_code التفرّد ويجعل بحث إعادة التوجيه O(log n). عند 365 مليار صف ستحتاج إلى تقسيم الجدول أو التشظية — المزيد حول ذلك لاحقاً.
الخطوة 4 — تدفق إعادة التوجيه
إعادة التوجيه عملية قراءة مكثّفة وحساسة للزمن. الاستعلام المباشر من قاعدة البيانات عند كل نقرة لا يصمد أمام 11,600 طلب/ثانية مع اشتراط زمن استجابة أقل من 10 ملي ثانية.
الخطوة 5 — 301 مقابل 302
قرار دقيق ذو تأثير كبير:
- 301 دائم: يخزّن المتصفح التوجيه للأبد. النقرات التالية تتجاوز خوادمك بالكامل — لا تحليلات، لكن أداء أقصى.
- 302 مؤقت: يستعلم المتصفح خوادمك في كل مرة. تحصل على إحصاءات نقرات دقيقة مقابل كل إعادة توجيه تمرّ عبر بنيتك.
معظم خدمات الاختصار التجارية (Bitly، TinyURL) تفضّل 302 لتتبّع النقرات. إن لم تكن التحليلات مطلوبة، يُخفّف 301 الحمل على الخوادم بشكل جذري.
الخطوة 6 — توسيع عمليات القراءة بالكاش
إعادة توجيه الروابط قراءة مكثّفة ذات موقعية زمنية عالية — تغريدة واحدة تنتشر قد تُولّد ملايين النقرات على كود واحد في دقائق. يتعامل مجموعة Redis مع هذه الذروة عبر استراتيجية write-through / read-aside:
- عند الكتابة: خزّن
short_code → long_urlفي Redis مع مهلة انتهاء تساوي انتهاء الرابط. - عند إعادة التوجيه: تحقق من Redis أولاً. عند الإصابة أعد الرد فوراً (أقل من ملي ثانية). عند الفشل، استعلم قاعدة البيانات واملأ الكاش.
- معدل إصابة الكاش للروابط الشائعة يتخطى 99 %. الروابط الباردة فقط تصل إلى قاعدة البيانات.
الخطوة 7 — توسيع عمليات الكتابة بالتشظية
عند 365 مليار صف خلال 10 سنوات، نسخة MySQL وحيدة غير عملية. الاستراتيجيات الشائعة:
- التشظية بالتجزئة المتسقة على
short_code: يُوجَّه كل كود إلى شظية محددة حتمياً. بسيط، لكن إعادة التشظية مؤلمة. - التشظية بالنطاق على المعرّف: IDs 0–1 مليار على الشظية الأولى، 1–2 مليار على الثانية، إلخ. سهل الاستيعاب، لكن الشظايا الحديثة تُصبح ساخنة إذ تُستهدف المعرّفات الأحدث أكثر.
- Vitess / PlanetScale: متوافق مع MySQL مع تشظية شفّافة مدمجة — الخيار الموصى به في الإنتاج.
الخطوة 8 — الأسماء المخصصة وانتهاء الصلاحية
غالباً يريد المستخدمون المميزون tinyurl.com/my-brand. تمرّ الأسماء المخصصة عبر نفس فحص التفرّد لكنها تتجاوز العدّاد — تحاول مباشرة إدراج الكود الذي اختاره المستخدم وتُعيد خطأ تعارض إن كان محجوزاً. أما انتهاء الصلاحية، فيتولاه عامل خلفي يفحص الصفوف حيث expires_at < NOW() ويحذفها أو يعلّمها غير نشطة. بعد فترة إضافية، يمكن إعادة استخدام الكودات المنتهية.
الخطوة 9 — التحليلات
عدّ النقرات بشكل متزامن عند كل إعادة توجيه يُضيف حمل كتابة على المسار الحرج. بدلاً من ذلك، أطلق حدثاً إلى طابور رسائل (Kafka، SQS) ودع مستهلكاً غير متزامن يُحدّث عمود click_count على دفعات. للتحليلات الأعمق (الموقع الجغرافي، الجهاز، المصدر)، اكتب كل حدث في مخزن عمودي (BigQuery، ClickHouse) — لا على شظايا MySQL التشغيلية أبداً.
ملخص: القرارات التصميمية الرئيسية
- توليد الكود القصير: عدّاد موزع + ترميز Base-62 — خالٍ من التعارضات وقابل للتوسع الأفقي.
- إعادة التوجيه: 302 للتحليلات، الكاش أولاً (Redis) لحماية قاعدة البيانات.
- التخزين: MySQL المشظّى (أو Vitess) لـ365 مليار صف خلال 10 سنوات.
- التحليلات: غير متزامن عبر طابور رسائل — لا على المسار الحرج لإعادة التوجيه.
- انتهاء الصلاحية: عامل خلفي، لا داخل مسار إعادة التوجيه.