في هذا البرنامج التعليمي، سنقوم ببناء خدمة تقصير عناوين URL قابلة للتوسع باستخدام Node.js و Redis. ستستفيد هذه الخدمة من التخزين المؤقت الموزع لمعالجة حركة المرور العالية بكفاءة، وتقليل التأخير، والتوسع بسهولة. سنكتشف مفاهيم رئيسية مثل التجزئة المتسقة، واستراتيجيات إبطال التخزين المؤقت، والتجزئة لضمان بقاء النظام سريعًا وموثوقًا.
بحلول نهاية هذا الدليل، ستمتلك خدمة تقصير عناوين URL متكاملة تستخدم التخزين الموزع لتحسين الأداء. سنقوم أيضًا بإنشاء عرض توضيحي تفاعلي حيث يمكن للمستخدمين إدخال عناوين URL ورؤية مقاييس الوقت الحقيقي مثل نجاحات التخزين المؤقت والفشل.
ما ستتعلمه
-
كيفية بناء خدمة تقصير عناوين URL باستخدام Node.js و Redis.
-
كيفية تنفيذ التخزين المؤقت الموزع لتحسين الأداء.
-
فهم التجزئة المتسقة و استراتيجيات إبطال التخزين المؤقت.
-
استخدام Docker لمحاكاة عدة مثيلات Redis للتجزئة والتوسيع.
المتطلبات
قبل البدء، تأكد من تثبيت ما يلي:
-
Node.js (الإصدار 14 أو أعلى)
-
Redis
-
Docker
-
المعرفة الأساسية في JavaScript وNode.js وRedis.
جدول المحتويات
نظرة عامة على المشروع
سنقوم ببناء خدمة تقصير الروابط حيث:
-
يمكن للمستخدمين تقصير الروابط الطويلة واسترجاع الروابط الأصلية.
-
تستخدم الخدمة ذاكرة التخزين المؤقت Redis لتخزين التعيينات بين الروابط المختصرة والروابط الأصلية.
-
يتم توزيع التخزين المؤقت عبر عدة مثيلات Redis للتعامل مع حركة مرور عالية.
-
سيظهر النظام ضربات الذاكرة المؤقتة والضربات الفائتة في الوقت الفعلي.
معمارية النظام
لضمان القابلية للتوسع والأداء، سنقسم خدمتنا إلى المكونات التالية:
-
خادم واجهة برمجة التطبيقات: يتعامل مع الطلبات لتقصير واسترجاع عناوين URL.
-
طبقة التخزين المؤقت Redis: تستخدم عدة مثيلات من Redis للتخزين المؤقت الموزع.
-
دوكر: يحاكي بيئة موزعة مع حاويات Redis متعددة.
الخطوة 1: إعداد المشروع
دعونا نبدأ مشروعنا من خلال تهيئة تطبيق Node.js:
mkdir scalable-url-shortener
cd scalable-url-shortener
npm init -y
الآن، قم بتثبيت التبعيات اللازمة:
npm install express redis shortid dotenv
-
express
: إطار عمل خادم ويب خفيف الوزن. -
redis
: للتعامل مع التخزين المؤقت. -
shortid
: لتوليد معرفات قصيرة وفريدة. -
dotenv
: لإدارة متغيرات البيئة.
إنشاء ملف .env في جذر مشروعك:
PORT=3000
REDIS_HOST_1=localhost
REDIS_PORT_1=6379
REDIS_HOST_2=localhost
REDIS_PORT_2=6380
REDIS_HOST_3=localhost
REDIS_PORT_3=6381
تعرف هذه المتغيرات على مضيفي Redis ومنافذ البيانات التي سنستخدمها.
الخطوة 2: إعداد حالات Redis
سنستخدم Docker لمحاكاة بيئة موزعة مع عدة حالات Redis.
تشغيل الأوامر التالية لبدء ثلاث حاويات Redis:
docker run -p 6379:6379 --name redis1 -d redis
docker run -p 6380:6379 --name redis2 -d redis
docker run -p 6381:6379 --name redis3 -d redis
سيتم إعداد ثلاث حالات Redis تعمل على منافذ مختلفة. سنستخدم هذه الحالات لتنفيذ التجزئة المتسقة والتجزئة.
الخطوة 3: تنفيذ خدمة تقصير عناوين URL
لنقم بإنشاء ملف التطبيق الرئيسي الخاص بنا، index.js:
require('dotenv').config();
const express = require('express');
const redis = require('redis');
const shortid = require('shortid');
const app = express();
app.use(express.json());
const redisClients = [
redis.createClient({ host: process.env.REDIS_HOST_1, port: process.env.REDIS_PORT_1 }),
redis.createClient({ host: process.env.REDIS_HOST_2, port: process.env.REDIS_PORT_2 }),
redis.createClient({ host: process.env.REDIS_HOST_3, port: process.env.REDIS_PORT_3 })
];
// دالة التجزئة لتوزيع المفاتيح بين عملاء Redis
function getRedisClient(key) {
const hash = key.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
return redisClients[hash % redisClients.length];
}
// النقطة النهائية لتقصير عنوان URL
app.post('/shorten', async (req, res) => {
const { url } = req.body;
if (!url) return res.status(400).send('URL is required');
const shortId = shortid.generate();
const redisClient = getRedisClient(shortId);
await redisClient.set(shortId, url);
res.json({ shortUrl: `http://localhost:${process.env.PORT}/${shortId}` });
});
// النقطة النهائية لاسترداد العنوان URL الأصلي
app.get('/:shortId', async (req, res) => {
const { shortId } = req.params;
const redisClient = getRedisClient(shortId);
redisClient.get(shortId, (err, url) => {
if (err || !url) {
return res.status(404).send('URL not found');
}
res.redirect(url);
});
});
app.listen(process.env.PORT, () => {
console.log(`Server running on port ${process.env.PORT}`);
});
كما يمكنك رؤية في هذا الكود، لدينا:
-
التجزئة المتسقة:
-
نقوم بتوزيع المفاتيح (عناوين URL المقصرة) عبر عدة عملاء Redis باستخدام دالة تجزئة بسيطة.
-
تضمن الدالة تجزئة توزيع العناوين URL بشكل متساو عبر حالات Redis.
-
-
تقصير الروابط:
-
تقبل نقطة النهاية /shorten عنوان URL طويل وتنشئ معرفًا قصيرًا باستخدام مكتبة shortid.
-
تُخزن الرابط المختصر في إحدى حالات Redis باستخدام وظيفتنا للتجزئة.
-
-
إعادة توجيه الروابط:
-
تسترجع نقطة النهاية /:shortId العنوان الأصلي من الذاكرة المؤقتة وتعيد توجيه المستخدم.
-
إذا لم يتم العثور على الرابط في الذاكرة المؤقتة، يتم إرجاع استجابة 404.
-
الخطوة 4: تنفيذ إلغاء تخزين المؤقت
في تطبيق العالم الحقيقي، قد تنتهي صلاحية الروابط أو تتغير مع مرور الوقت. للتعامل مع هذا، نحتاج إلى تنفيذ إلغاء تخزين المؤقت.
إضافة انتهاء الصلاحية لعناوين الروابط المخزنة
لنعدل ملف index.js لنضبط وقت الانتهاء لكل إدخال مخزن:
// نقطة النهاية لتقصير عنوان URL بانتهاء الصلاحية
app.post('/shorten', async (req, res) => {
const { url, ttl } = req.body; // ttl (الوقت للحياة) اختياري
if (!url) return res.status(400).send('URL is required');
const shortId = shortid.generate();
const redisClient = getRedisClient(shortId);
await redisClient.set(shortId, url, 'EX', ttl || 3600); // TTL الافتراضي هو 1 ساعة
res.json({ shortUrl: `http://localhost:${process.env.PORT}/${shortId}` });
});
-
TTL (الوقت للحياة): نضبط وقت انتهاء افتراضي بـ 1 ساعة لكل عنوان URL مقصر. يمكنك تخصيص TTL لكل عنوان URL حسب الحاجة.
-
إلغاء تخزين المؤقت: عند انتهاء TTL، يتم إزالة الإدخال تلقائيًا من ذاكرة التخزين المؤقت.
الخطوة 5: مراقبة مقاييس التخزين المؤقت
لمراقبة ضربات الذاكرة المؤقتة و الفقدان، سنضيف بعض تسجيلات إلى نقاط النهاية الخاصة بنا في index.js:
app.get('/:shortId', async (req, res) => {
const { shortId } = req.params;
const redisClient = getRedisClient(shortId);
redisClient.get(shortId, (err, url) => {
if (err || !url) {
console.log(`Cache miss for key: ${shortId}`);
return res.status(404).send('URL not found');
}
console.log(`Cache hit for key: ${shortId}`);
res.redirect(url);
});
});
هذا ما يحدث في هذا الكود:
-
ضربات الذاكرة المؤقتة: إذا تم العثور على عنوان URL في الذاكرة المؤقتة، فهذا يعتبر ضربة ذاكرة مؤقتة.
-
الفقدان في الذاكرة المؤقتة: إذا لم يتم العثور على عنوان URL، فهذا يعتبر فقدان في الذاكرة المؤقتة.
-
سيساعدك هذا التسجيل في مراقبة أداء ذاكرتك المؤقتة الموزعة.
الخطوة 6: اختبار التطبيق
- ابدأ حالات Redis الخاصة بك:
docker start redis1 redis2 redis3
- شغل خادم Node.js:
node index.js
-
اختبار نقاط النهاية باستخدام
curl
أو Postman:-
قص الرابط:
POST http://localhost:3000/shorten Body: { "url": "https://example.com" }
-
الوصول إلى الرابط المختصر:
GET http://localhost:3000/{shortId}
-
استنتاج: ماذا تعلمت
تهانينا! لقد بنيت بنجاح خدمة اختصار عناوين URL قابلة للتوسع باستخدام ذاكرة التخزين المؤقت الموزعة باستخدام Node.js وRedis. خلال هذا البرنامج التعليمي، تعلمت كيفية:
-
نفذ تجزئة متسقة لتوزيع إدخالات الذاكرة المؤقتة عبر عدة مثيلات من Redis.
-
قم بتحسين تطبيقك باستراتيجيات إبطال الذاكرة المؤقتة للحفاظ على تحديث البيانات.
-
استخدم دوكر لمحاكاة بيئة موزعة مع عدة عقد Redis.
-
راقب أوقات ضربات وفقدان الذاكرة المؤقتة لتحسين الأداء.
الخطوات التالية:
-
إضافة قاعدة بيانات: قم بتخزين عناوين URL في قاعدة بيانات للصمود ما بعد الذاكرة المؤقتة.
-
نفذ تحليلات: تتبع عدد النقرات والتحليلات لعناوين URL المختصرة.
-
نشر إلى السحابة: نشر تطبيقك باستخدام Kubernetes للتوسيع التلقائي والصمود.
نتمنى لك تجربة برمجية ممتعة!