استخدامات Redis: التخزين المؤقت وقوائم الانتظار وتحديد المعدل
لقد قمت ببناء تطبيق ويب يعمل بشكل جيد مع عدد قليل من المستخدمين، ولكن مع نمو حركة المرور، تلاحظ بطء تحميل الصفحات، وتكرار المهام في الخلفية، وتعرض واجهات برمجة التطبيقات (APIs) لهجمات من عملاء مسيئين. هذه مشكلات توسع كلاسيكية يمكن لـ Redis حلها بأناقة. Redis هو مخزن بيانات في الذاكرة يتميز في ثلاث مهام أساسية: التخزين المؤقت، وقوائم الانتظار، وتحديد المعدل. في هذه المقالة، سنستكشف كل حالة استخدام مع أمثلة عملية وأفضل الممارسات.
لماذا Redis لهذه الاستخدامات؟
يخزن Redis البيانات في الذاكرة، مما يجعله سريعًا بشكل لا يصدق - غالبًا بأوقات استجابة أقل من مللي ثانية. يدعم هياكل بيانات غنية مثل السلاسل النصية، والقوائم، والمجموعات، والمجموعات المرتبة، والتجزئات (hashes)، والتي تتوافق بشكل طبيعي مع التخزين المؤقت وقوائم الانتظار وتحديد المعدل. كما يوفر عمليات ذرية (atomic)، ونظام النشر/الاشتراك (pub/sub)، وخيارات الاستمرارية، مما يجعله أداة متعددة الاستخدامات لمطوري الواجهة الخلفية.
1. التخزين المؤقت باستخدام Redis
التخزين المؤقت هو حالة الاستخدام الأكثر شيوعًا لـ Redis. من خلال تخزين البيانات التي يتم الوصول إليها بشكل متكرر في Redis، تقلل من حمل قاعدة البيانات وتسرع الاستجابات.
كيف يعمل التخزين المؤقت
عند وصول طلب، تحقق مما إذا كانت البيانات موجودة في Redis. إذا كانت موجودة (إصابة ذاكرة التخزين المؤقت)، فأعدها مباشرة. إذا لم تكن موجودة (فشل ذاكرة التخزين المؤقت)، فاجلبها من قاعدة البيانات، وخزّنها في Redis مع وقت انتهاء الصلاحية، ثم أعدها. يُسمى هذا النمط بالتخزين المؤقت الجانبي (cache-aside).
// Node.js example using ioredis
const Redis = require('ioredis');
const redis = new Redis();
async function getUser(userId) {
const cacheKey = `user:${userId}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600); // 1 hour TTL
return user;
}
أفضل الممارسات للتخزين المؤقت
- عيّن TTL: دائمًا عيّن وقت انتهاء الصلاحية لتجنب البيانات القديمة وتضخم الذاكرة.
- استخدم هياكل البيانات المناسبة: للقيم البسيطة، استخدم السلاسل النصية. للكائنات، استخدم التجزئات لتحديث الحقول بشكل فردي.
- تعامل مع إبطال ذاكرة التخزين المؤقت: قم بتحديث أو حذف إدخالات ذاكرة التخزين المؤقت عندما تتغير البيانات الأساسية.
- راقب معدل الإصابة: معدل إصابة منخفض يعني أن ذاكرة التخزين المؤقت غير فعالة؛ اضبط المفاتيح أو TTLs.
استراتيجيات إبطال ذاكرة التخزين المؤقت
الإبطال صعب. تشمل الاستراتيجيات الشائعة:
- قائم على الوقت: اعتمد على TTL. بسيط ولكن قد يقدم بيانات قديمة.
- الكتابة من خلال (Write-through): حدّث ذاكرة التخزين المؤقت كلما قمت بتحديث قاعدة البيانات.
- الكتابة خلف (Write-behind): حدّث ذاكرة التخزين المؤقت أولاً، ثم حدّث قاعدة البيانات بشكل غير متزامن (خطر فقدان البيانات).
2. قوائم الانتظار باستخدام Redis
تفصل قوائم الانتظار المهام التي تستغرق وقتًا طويلاً عن دورة الطلب والاستجابة. على سبيل المثال، يمكن إرسال البريد الإلكتروني، أو معالجة الصور، أو إنشاء التقارير بشكل غير متزامن.
تنفيذ قائمة انتظار بسيطة
قوائم Redis مثالية لقوائم الانتظار. استخدم LPUSH لإضافة المهام إلى اليسار و BRPOP للحظر والانتظار حتى تظهر من اليمين (FIFO).
// Producer: add job
await redis.lpush('email_queue', JSON.stringify({ to: 'user@example.com', subject: 'Welcome' }));
// Consumer: process job (in a worker)
while (true) {
const job = await redis.brpop('email_queue', 0); // 0 = block indefinitely
const { to, subject } = JSON.parse(job[1]);
await sendEmail(to, subject);
}
قوائم انتظار موثوقة باستخدام BRPOPLPUSH
لتجنب فقدان المهام إذا تعطل عامل، استخدم BRPOPLPUSH لنقل المهمة بشكل ذري إلى قائمة المعالجة. بعد المعالجة الناجحة، قم بإزالتها من قائمة المعالجة. إذا مات العامل، يمكن لعامل آخر استرداد المهام من قائمة المعالجة.
const job = await redis.brpoplpush('email_queue', 'processing_queue', 0);
try {
// process job
await redis.lrem('processing_queue', 1, job);
} catch (err) {
// handle error, maybe requeue
}
مكتبات قوائم الانتظار المخصصة
للإنتاج، فكر في مكتبات مثل Bull (Node.js)، أو RQ (Python)، أو Sidekiq (Ruby). فهي توفر إعادة المحاولات والجدولة والمراقبة جاهزة.
3. تحديد المعدل باستخدام Redis
يحمي تحديد المعدل واجهة برمجة التطبيقات الخاصة بك من الإساءة ويضمن الاستخدام العادل. تجعل العمليات الذرية لـ Redis مثاليًا لحساب الطلبات.
تحديد المعدل بنافذة ثابتة
قم بزيادة عداد لكل مستخدم لكل نافذة زمنية. إذا تجاوز العدد الحد المسموح، ارفض الطلب.
async function isAllowed(userId, limit = 100, windowSec = 60) {
const key = `rate:${userId}:${Math.floor(Date.now() / 1000 / windowSec)}`;
const count = await redis.incr(key);
if (count === 1) await redis.expire(key, windowSec);
return count