التخزين المؤقت في JavaScript: Redis وMemcached والحلول المدمجة (مستوى متقدم)
\n\nفي تطوير الويب الحديث، غالبًا ما يعتمد أداء التطبيق على سرعة الوصول إلى البيانات. كل طلب إضافي لقاعدة البيانات أو API خارجي يزيد من وقت الاستجابة والتحميل على الخادم. التخزين المؤقت ليس مجرد "حفظ النتيجة"، بل هو علم كامل يتضمن اختيار الاستراتيجية الصحيحة والأداة وآلية الإبطال. في هذه المقالة، سنستعرض التقنيات المتقدمة للتخزين المؤقت في Node.js: من الحلول المدمجة إلى الأنظمة الصناعية Redis وMemcached.
\n\n1. التخزين المؤقت المدمج في Node.js: البساطة والقيود
\n\nقبل ربط الخدمات الخارجية، يجدر تقييم إمكانيات Node.js نفسه. يتم تنفيذ التخزين المؤقت المدمج باستخدام كائنات Map أو WeakMap، بالإضافة إلى المتغيرات العامة البسيطة. هذا مثالي للبيانات التي نادرًا ما تتغير وتعيش ضمن عملية واحدة.
// أبسط ذاكرة تخزين مؤقت في الذاكرة مع TTL (وقت الحياة)\nclass MemoryCache {\n constructor(ttl = 60000) {\n this.cache = new Map();\n this.ttl = ttl;\n }\n\n set(key, value) {\n this.cache.set(key, {\n value,\n expiry: Date.now() + this.ttl\n });\n }\n\n get(key) {\n const item = this.cache.get(key);\n if (!item) return null;\n if (Date.now() > item.expiry) {\n this.cache.delete(key);\n return null;\n }\n return item.value;\n }\n\n delete(key) {\n this.cache.delete(key);\n }\n\n clear() {\n this.cache.clear();\n }\n}\n\n// الاستخدام\nconst cache = new MemoryCache(30000); // TTL 30 ثانية\ncache.set('user:42', { name: 'Alice', role: 'admin' });\nconsole.log(cache.get('user:42')); // { name: 'Alice', role: 'admin' }\n\n\nالإيجابيات: زمن انتقال صفري للشبكة، سهولة التنفيذ، لا يتطلب تبعيات خارجية.
\nالسلبيات: فقدان البيانات عند إعادة تشغيل العملية، لا يتوسع عبر خوادم متعددة، محدود بذاكرة الوصول العشوائي لعقدة واحدة.
بالنسبة للخدمات المصغرة التي تعمل في عملية واحدة، أو لتخزين نتائج الحسابات الثقيلة (مثل عرض القوالب)، فإن الحل المدمج هو اختيار ممتاز. ومع ذلك، فهو غير كافٍ للأنظمة الموزعة.
\n\n2. Redis: ملك التخزين المؤقت في Node.js
\n\nRedis هو مخزن بيانات في الذاكرة يُستخدم غالبًا كذاكرة تخزين مؤقت عالية الأداء. مزاياه الرئيسية: دعم هياكل البيانات المعقدة (سلاسل، قوائم، مجموعات، تجزئات)، TTL مدمج، pub/sub، والثبات على القرص.
\n\nربط Redis في Node.js
\n\nللعمل مع Redis في Node.js، يُستخدم الحزمة ioredis (موصى بها) أو redis. لنلقِ نظرة على مثال باستخدام ioredis:
const Redis = require('ioredis');\n\nconst redis = new Redis({\n host: 'localhost',\n port: 6379,\n retryStrategy: (times) => Math.min(times * 50, 2000) // استراتيجية إعادة الاتصال\n});\n\n// العمليات الأساسية\nawait redis.set('mykey', 'Hello, Redis!', 'EX', 60); // TTL 60 ثانية\nconst value = await redis.get('mykey');\nconsole.log(value); // "Hello, Redis!"\n\n// استخدام التجزئة لتخزين الكائنات المعقدة\nawait redis.hmset('user:100', {\n name: 'Bob',\n email: 'bob@example.com',\n age: 30\n});\nconst user = await redis.hgetall('user:100');\nconsole.log(user); // { name: 'Bob', email: 'bob@example.com', age: '30' }\n\n\nاستراتيجيات متقدمة مع Redis
\n\nCache-Aside (التحميل البطيء): يتحقق التطبيق أولاً من التخزين المؤقت، وعند عدم وجود البيانات فقط يتصل بقاعدة البيانات ويملأ التخزين المؤقت.
\n\nasync function getUser(userId) {\n const cacheKey = `user:${userId}`;\n let user = await redis.get(cacheKey);\n if (user) {\n return JSON.parse(user);\n }\n // عدم وجود البيانات في التخزين المؤقت - تحميل من قاعدة البيانات\n user = await db.findUserById(userId);\n if (user) {\n await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600); // تخزين مؤقت لمدة ساعة\n }\n ret