بدأ كل شيء بمهمة بسيطة - لعرض منتجات جديدة للمستخدم ، مع مراعاة تفضيلاته الفردية. وإذا لم تكن هناك مشاكل في الحصول على منتجات جديدة ، فإن ربط المنتجات الجديدة بالتفضيلات (تحليل الإحصائيات) أدى بالفعل إلى خلق عبء ملموس (على سبيل المثال ، دعنا نحدده في 4 ثوانٍ). كانت خصوصية المهمة هي أن المنظمات بأكملها يمكن أن تعمل كمستخدمين. وليس من غير المألوف أن يصل 200-300 طلب خاص بمستخدم واحد إلى الخادم مرة واحدة (في غضون 2-3 ثوانٍ). أولئك. يتم إنشاء نفس الكتلة للعديد من المستخدمين في وقت واحد.
الحل الواضح هو تخزينه في ذاكرة الوصول العشوائي (لن نعرض نظام DBMS للعنف ، مما يجبره على معالجة تدفق كبير من المكالمات). المخطط الكلاسيكي:
- جاء الطلب
- فحص ذاكرة التخزين المؤقت. إذا كانت تحتوي على بيانات ، ولم تكن قديمة ، فنحن نعيدها فقط.
- لا توجد بيانات => إنشاء مشكلة
- نرسل للمستخدم
- بالإضافة إلى ذلك ، نضيفه إلى ذاكرة التخزين المؤقت ، مشيرًا إلى TTL
عيب هذا الحل: إذا لم تكن هناك بيانات في ذاكرة التخزين المؤقت ، فإن جميع الطلبات التي وردت خلال الجيل الأول ستنشئها ، مما يؤدي إلى إنفاق موارد الخادم على هذا (ذروة التحميل). وبالطبع ، سينتظر جميع المستخدمين عند "الاتصال الأول".
لاحظ أيضًا أنه مع قيم ذاكرة التخزين المؤقت الفردية ، يمكن أن ينمو عدد السجلات بدرجة كبيرة بحيث لا تكون ذاكرة الوصول العشوائي المتوفرة للخادم كافية. ثم يبدو من المنطقي استخدام خادم HDD محلي كمخزن مؤقت. لكننا نفقد السرعة على الفور.
كيف تكون؟
أول ما يتبادر إلى الذهن: سيكون من الرائع تخزين السجلات في مكانين - في ذاكرة الوصول العشوائي (مطلوبة بشكل متكرر) ومحرك الأقراص الصلبة (الكل أو نادرًا ما يطلب ذلك). مفهوم "البيانات الساخنة والباردة" في أنقى صورها. هناك العديد من التطبيقات لهذا النهج ، لذلك لن نتطرق إليها. لنقم فقط بتعيين هذا المكون على أنه 2L. في حالتي ، يتم تنفيذه بنجاح استنادًا إلى Scylla DBMS.
ولكن كيف تتخلص من عمليات السحب عندما تكون ذاكرة التخزين المؤقت قديمة؟ وهنا نقوم بتضمين مفهوم 2R ، والذي يعد معناه شيئًا بسيطًا: بالنسبة لسجل ذاكرة التخزين المؤقت ، لا تحتاج إلى تحديد قيمة TTL واحدة ، ولكن 2. TTL1 هو طابع زمني ، مما يعني أن "البيانات قديمة ، ويجب إعادة إنشائها ، ولكن لا يزال بإمكانك استخدامها" ؛ TTL2 - "كل شيء قديم جدًا لدرجة أنه لم يعد من الممكن استخدامه".
وبالتالي ، نحصل على مخطط مختلف قليلاً للتخزين المؤقت:
- جاء الطلب
- نحن نبحث عن البيانات في ذاكرة التخزين المؤقت. إذا كانت البيانات موجودة ولم تكن قديمة (t <TTL1) - فإننا نعيدها إلى المستخدم ، كالمعتاد ولا نفعل شيئًا آخر.
- البيانات موجودة ، قديمة ، ولكن يمكن استخدامها (TTL1 <t <TTL2) - نعطيها للمستخدم ونقوم بتهيئة الإجراء لتحديث سجل ذاكرة التخزين المؤقت
- لا توجد بيانات على الإطلاق (تم قتلها بعد انتهاء صلاحية TTL2) - نقوم بإنشائها "كالمعتاد" ونكتبها في ذاكرة التخزين المؤقت.
- بعد تقديم المحتوى إلى المستخدم أو في دفق مواز ، نقوم بتنفيذ الإجراءات لتحديث سجلات ذاكرة التخزين المؤقت.
نتيجة لذلك ، لدينا:
- إذا تم استخدام سجلات ذاكرة التخزين المؤقت بشكل كافٍ ، فلن يجد المستخدم نفسه أبدًا في حالة "انتظار تحديث ذاكرة التخزين المؤقت" - سيتلقى دائمًا نتيجة جاهزة.
- إذا تم تنظيم قائمة انتظار "التحديثات" بشكل صحيح ، فمن الممكن تحقيق حقيقة أنه في حالة عدة عمليات وصول متزامنة إلى السجل باستخدام TTL1 <t <TTL2 ، ستكون مهمة واحدة فقط للتحديث في قائمة الانتظار ، وليس العديد من المهام المماثلة.
كمثال: بالنسبة لخلاصة الأخبار ، يمكنك تحديد TTL1 = ساعة واحدة (مع ذلك ، لا يظهر المحتوى الجديد بشكل مكثف) ، و TTL2 - أسبوع واحد.
في أبسط الحالات ، يمكن أن يكون كود PHP لتنفيذ 2R كما يلي:
$tmp = cache_get($key);
If (!$tmp){
$items = generate_items();
cache_set($items, 60*60, 60*60*24*7);
}else{
$items = $tmp[‘items’];
If (time()-$tmp[‘tm’] > 60*60){
$need_rebuild[] = [‘to’=>$key, ‘method’=>’generate_items’];
}
}
…
//
echo json_encode($items);
…
// ,
If (isset($need_rebuild) && count($need_rebuild)>0){
foreach($need_rebuild as $k=>$v){
$tmp = ['tm'=>time(), 'items'=>$$v[‘method’]];
cache_set($tmp, 60*60, 60*60*24*7);
}
}
من الناحية العملية ، بالطبع ، من المرجح أن يكون التنفيذ أكثر صعوبة. على سبيل المثال ، مُنشئ سجلات ذاكرة التخزين المؤقت هو برنامج نصي منفصل يتم إطلاقه كخدمة ؛ قائمة الانتظار - عبر Rabbit ، العلامة "مثل هذا المفتاح موجود بالفعل في قائمة الانتظار للتجديد" - عبر Redis أو Scylla.
لذلك ، إذا قمنا بدمج نهج "النطاقين" ومفهوم البيانات "الساخنة / الباردة" ، نحصل على - 2R2L.
شكر!