استراتيجيات إبطال التخزين المؤقت
استراتيجيات إبطال التخزين المؤقت
إبطال التخزين المؤقت هو أحد أصعب المشاكل في علوم الحاسوب. يغطي هذا الدرس الاستراتيجيات المثبتة للحفاظ على التخزين المؤقت محدثاً ومتسقاً مع مصادر البيانات.
الانتهاء المبني على TTL
أبسط استراتيجية للإبطال هي تعيين وقت البقاء (TTL) على العناصر المخزنة مؤقتاً:
// تعيين التخزين المؤقت بمدة ساعة واحدة
Cache::put('user:' . $userId, $userData, 3600);
// أو استخدام Redis مباشرة
Redis::setex('product:' . $productId, 3600, json_encode($product));
// التحقق من الوقت المتبقي
$ttl = Redis::ttl('user:' . $userId); // يعيد الثواني المتبقيةالإبطال المبني على الأحداث
إبطال التخزين المؤقت عند تغيير البيانات الأساسية:
// في النموذج أو المتحكم الخاص بك
public function updateUser($userId, $data)
{
// تحديث قاعدة البيانات
$user = User::findOrFail($userId);
$user->update($data);
// إبطال التخزين المؤقت
Cache::forget('user:' . $userId);
Cache::tags(['users'])->flush(); // إذا كنت تستخدم وسوم التخزين المؤقت
return $user;
}الإبطال المبني على الوسوم
تجميع مفاتيح التخزين المؤقت ذات الصلة تحت وسوم للإبطال الجماعي:
// التخزين مع الوسوم
Cache::tags(['users', 'user:' . $userId])->put('profile', $data, 3600);
Cache::tags(['users', 'admins'])->put('admin:list', $admins, 7200);
// إبطال جميع التخزينات المؤقتة المتعلقة بالمستخدمين
Cache::tags(['users'])->flush();
// إبطال مستخدم محدد
Cache::tags(['user:' . $userId])->flush();إزالة التخزين المؤقت المبنية على الإصدار
إلحاق رقم إصدار بمفاتيح التخزين المؤقت بدلاً من حذف البيانات القديمة:
// تخزين الإصدار الحالي في Redis
Redis::set('cache_version:users', 1);
// إنشاء مفتاح التخزين المؤقت مع الإصدار
$version = Redis::get('cache_version:users');
$cacheKey = "users:list:v{$version}";
$users = Cache::get($cacheKey);
// للإبطال، زيادة الإصدار
Redis::incr('cache_version:users'); // الآن الإصدار هو 2
// التخزين المؤقت القديم في v1 يصبح قديماً تلقائياًمنع اندفاع التخزين المؤقت
عندما ينتهي التخزين المؤقت على المفاتيح ذات الحركة المرتفعة، قد تحاول طلبات متعددة في وقت واحد إعادة بناء التخزين المؤقت (اندفاع). الحلول:
public function getUserData($userId)
{
$cacheKey = 'user:' . $userId;
$lockKey = $cacheKey . ':lock';
// محاولة الحصول من التخزين المؤقت
$data = Cache::get($cacheKey);
if ($data) return $data;
// الحصول على القفل (مهلة 5 ثوانٍ)
$lock = Cache::lock($lockKey, 5);
try {
if ($lock->get()) {
// التحقق المزدوج من التخزين المؤقت (قد تكون عملية أخرى قد ملأته)
$data = Cache::get($cacheKey);
if ($data) return $data;
// إعادة بناء التخزين المؤقت
$data = User::find($userId);
Cache::put($cacheKey, $data, 3600);
return $data;
} else {
// لم يتمكن من الحصول على القفل، انتظر قليلاً وأعد المحاولة
usleep(100000); // 100 ملي ثانية
return Cache::get($cacheKey) ?? User::find($userId);
}
} finally {
$lock->release();
}
}public function getWithProbabilisticRefresh($key, $ttl, $callback)
{
$data = Redis::get($key);
if ($data) {
// الحصول على TTL المتبقي
$remaining = Redis::ttl($key);
// حساب احتمالية التحديث المبكر
// عندما يقترب TTL من 0، يزداد الاحتمال
$probability = 1 - ($remaining / $ttl);
if (mt_rand() / mt_getrandmax() < $probability) {
// تحديث التخزين المؤقت احتمالياً في الخلفية
dispatch(new RefreshCacheJob($key, $callback));
}
return json_decode($data, true);
}
// فقدان التخزين المؤقت - إعادة البناء
$data = $callback();
Redis::setex($key, $ttl, json_encode($data));
return $data;
}نمط القديم أثناء إعادة التحقق
تقديم التخزين المؤقت القديم أثناء التحديث في الخلفية:
public function getWithStaleWhileRevalidate($key, $freshTtl, $staleTtl, $callback)
{
$data = Redis::get($key);
$freshUntil = Redis::get($key . ':fresh_until');
if ($data) {
$now = time();
if ($freshUntil && $now < $freshUntil) {
// البيانات لا تزال طازجة
return json_decode($data, true);
}
// البيانات قديمة ولكن قابلة للاستخدام - تشغيل التحديث في الخلفية
if (Redis::set($key . ':refreshing', 1, 'EX', 60, 'NX')) {
// حصلنا على قفل التحديث
dispatch(new RefreshCacheJob($key, $freshTtl, $staleTtl, $callback));
}
// إرجاع البيانات القديمة فوراً
return json_decode($data, true);
}
// فقدان التخزين المؤقت - إعادة البناء المتزامن
$data = $callback();
Redis::setex($key, $staleTtl, json_encode($data));
Redis::setex($key . ':fresh_until', $staleTtl, time() + $freshTtl);
return $data;
}شجرة قرار إبطال التخزين المؤقت
1. الانتهاء المبني على TTL ✓ بيانات بسيطة وقابلة للتنبؤ ✓ متطلبات اتساق منخفضة ✓ مثال: لوحات تحليلات، التغذيات 2. الإبطال المبني على الأحداث ✓ متطلبات اتساق عالية ✓ أحداث تغيير بيانات واضحة ✓ مثال: ملفات المستخدمين، تفاصيل المنتجات 3. الإبطال المبني على الوسوم ✓ مجموعات بيانات ذات صلة ✓ احتياجات إبطال جماعي ✓ مثال: منتجات الفئات، أذونات المستخدمين 4. الإزالة المبنية على الإصدار ✓ منطق إبطال معقد ✓ احتياجات طرح تدريجي ✓ مثال: التكوينات، علامات الميزات 5. القديم أثناء إعادة التحقق ✓ توليد بيانات مكلف ✓ مفاتيح حركة مرتفعة ✓ بيانات قديمة مقبولة ✓ مثال: بيانات الصفحة الرئيسية، العناصر الرائجة
- تنفيذ تخزين مؤقت للمنتجات مع إبطال مبني على الوسوم (الوسوم: products، category:{id}، brand:{id})
- إضافة منع اندفاع التخزين المؤقت باستخدام الأقفال
- إنشاء طريقة لإبطال جميع المنتجات في فئة عند تحديث الفئة
- تنفيذ القديم أثناء إعادة التحقق لتغذية الصفحة الرئيسية مع TTL طازج 5 دقائق وTTL قديم ساعة واحدة
- إضافة مراقبة لتتبع معدلات نجاح التخزين المؤقت وحدوث الاندفاع