التخزين المؤقت لارافيل: أساسيات بالإضافة إلى نصائح وحيل

تتيح لك تقنية التخزين المؤقت إنشاء تطبيقات أكثر قابلية للتطوير ، وتخزين نتائج بعض الاستعلامات في وحدة تخزين سريعة في الذاكرة. ومع ذلك ، يمكن أن يؤدي التخزين المؤقت الذي تم تنفيذه بشكل غير صحيح إلى تقليل انطباع المستخدم عن التطبيق إلى حد كبير. تحتوي هذه المقالة على بعض المفاهيم الأساسية حول التخزين المؤقت والقواعد المختلفة والمحرمات التي تعلمتها من عدة مشاريع سابقة.


لا تستخدم التخزين المؤقت.


هل مشروعك سريع وليس لديه مشاكل في الأداء؟
نسيان التخزين المؤقت. بجدية :)


سوف يعقد إلى حد كبير عمليات القراءة من قاعدة البيانات دون أي فوائد.


صحيح أن محمد سعيد في بداية هذه المقالة يقوم ببعض الحسابات ويثبت أنه في بعض الحالات ، يمكن أن يؤدي تحسين تطبيق ميلي ثانية إلى توفير الكثير من المال في حساب AWS الخاص بك. لذلك إذا كانت المدخرات المتوقعة في مشروعك تزيد عن 1.86 دولار ، فربما يكون التخزين المؤقت فكرة جيدة.


كيف يعمل؟


عندما يريد أحد التطبيقات الحصول على بعض البيانات من قاعدة البيانات ، على سبيل المثال ، كيان 'post_' . $id ، فإنه ينشئ مفتاح تخزين 'post_' . $id فريدًا لهذه الحالة ( 'post_' . $id مناسب تمامًا) ويحاول العثور على القيمة بواسطة هذا المفتاح في التخزين السريع ذي القيمة الرئيسية (memcache ، redis ، أو آخر). إذا كانت القيمة موجودة ، فسيستخدمها التطبيق. إذا لم يكن كذلك ، فإنه يأخذ من قاعدة البيانات والمخازن في ذاكرة التخزين المؤقت بواسطة هذا المفتاح للاستخدام في المستقبل.



لا يعد الاحتفاظ بهذه القيمة في ذاكرة التخزين المؤقت فكرة جيدة إلى الأبد ، حيث يمكن تحديث كيان النشر هذا ، لكن سيتلقى التطبيق دائمًا القيمة القديمة المخزنة مؤقتًا.
لذلك ، عادةً ما تطلب وظائف التخزين المؤقت وقت تخزين هذه القيمة.


بعد انتهاء هذا الوقت ، "memcache أو redis" سوف "ينساه" وسوف يأخذ التطبيق قيمة جديدة من قاعدة البيانات.


مثال:


 public function getPost($id): Post { $key = 'post_' . $id; $post = \Cache::get($key); if($post === null) { $post = Post::findOrFail($id); \Cache::put($key, $post, 900); } return $post; } 

أنا هنا أضع كيان البريد في ذاكرة التخزين المؤقت لمدة 15 دقيقة (منذ الإصدار 5.8 ، يستخدم laravel ثواني في هذه المعلمة ، قبل دقائق). تحتوي واجهة Cache أيضًا على طريقة remember ملائمة لهذه الحالة. يعمل هذا الكود تمامًا مثل الكود السابق:


 public function getPost($id): Post { return \Cache::remember('post_' . $id, 900, function() use ($id) { return Post::findOrFail($id); }); } 

يوجد فصل ذاكرة التخزين المؤقت في وثائق Laravel التي توضح كيفية تثبيت برامج التشغيل الضرورية للتطبيق والوظائف الرئيسية.


البيانات المخزنة مؤقتا


جميع السائقين Laravel القياسية تخزين البيانات على شكل سلاسل. عندما نطلب منك التخزين المؤقت لمثيل طراز Eloquent ، فإنه يستخدم الدالة التسلسلية للحصول على السلسلة من الكائن. تقوم الدالة غير المُسلسلة باستعادة حالة الكائن عندما نحصل عليه من ذاكرة التخزين المؤقت.


يمكن تقريبًا تخزين أي بيانات تقريبًا. الأرقام والسلاسل والمصفوفات والكائنات (إذا كان من الممكن إجراء تسلسل صحيح لها ، فراجع أوصاف الوظائف من الروابط السابقة).


يمكن تخزين الكيانات والمجموعات البليغة بسهولة وتُعد أكثر القيم شيوعًا في ذاكرة التخزين المؤقت للتطبيق Laravel. ومع ذلك ، فإن ممارسة الأنواع الأخرى تمارس على نطاق واسع. أسلوب Cache::increment شائع لتطبيق عدادات متعددة. أيضا ، الأقفال الذرية مفيدة للغاية عندما يقاتل المطورون ظروف السباق .


ما لذاكرة التخزين المؤقت؟


أول المرشحين للتخزين المؤقت هي الطلبات التي يتم تنفيذها في كثير من الأحيان ، ولكن خطة التنفيذ الخاصة بهم ليست هي الأسهل. أفضل مثال على ذلك هو أفضل 5 مقالات في الصفحة الرئيسية ، أو آخر الأخبار. يمكن أن يؤدي تخزين هذه القيم مؤقتًا إلى تحسين أداء الصفحة الرئيسية بشكل كبير.


عادةً ما يكون جلب الكيانات باستخدام المعرف باستخدام Model::find($id) سريعًا للغاية ، ولكن إذا كان هذا الجدول محملاً كثيرًا بالعديد من التحديثات ، قم بإدراج وحذف الاستعلامات ، مما يقلل من عدد الاستعلامات المحددة التي ستوفر فترة راحة جيدة لقاعدة البيانات. الكيانات التي لها علاقات hasMany والتي سيتم تحميلها في كل مرة هي أيضًا مرشح جيد للتخزين المؤقت. عندما عملت في مشروع مع أكثر من 10 ملايين زائر يوميًا ، قمنا بتخزين أي طلب تحديد مؤقتًا تقريبًا.


إبطال ذاكرة التخزين المؤقت


يساعد تسوس المفاتيح بعد وقت محدد في تحديث البيانات الموجودة في ذاكرة التخزين المؤقت ، ولكن هذا لا يحدث على الفور. يمكن للمستخدم تغيير البيانات ، ولكن لبعض الوقت سيستمر في رؤية الإصدار القديم منها في التطبيق. الحوار المعتاد حول أحد مشاريعي السابقة:


 :   ,     ! : ,  15 ( ,  )... 

هذا السلوك غير مريح للغاية للمستخدمين ، والقرار الواضح بحذف البيانات القديمة من ذاكرة التخزين المؤقت عندما نقوم بتحديثها بسرعة يتبادر إلى الذهن. هذه العملية تسمى العجز. بالنسبة للمفاتيح البسيطة مثل "post_%id%" ، فإن عملية "post_%id%" ليست صعبة للغاية.


يمكن أن تساعد الأحداث البليغة ، أو إذا كان تطبيقك ينشئ أحداثًا خاصة مثل PostPublished أو UserBanned فقد يكون الأمر أكثر بساطة. مثال مع الأحداث الفصيحة. تحتاج أولاً إلى إنشاء فصول حدث. للراحة ، سأستخدم فئة تجريدية لهم:


 abstract class PostEvent { /** @var Post */ private $post; public function __construct(Post $post) { $this->post = $post; } public function getPost(): Post { return $this->post; } } final class PostSaved extends PostEvent{} final class PostDeleted extends PostEvent{} 

بالطبع ، وفقًا لـ PSR-4 ، يجب أن يكون كل فصل في ملفه الخاص. قم بإعداد فئة Post Eloquent (باستخدام الوثائق ):


 class Post extends Model { protected $dispatchesEvents = [ 'saved' => PostSaved::class, 'deleted' => PostDeleted::class, ]; } 

إنشاء مستمع لهذه الأحداث:


 class EventServiceProvider extends ServiceProvider { protected $listen = [ PostSaved::class => [ ClearPostCache::class, ], PostDeleted::class => [ ClearPostCache::class, ], ]; } class ClearPostCache { public function handle(PostEvent $event) { \Cache::forget('post_' . $event->getPost()->id); } } 

سيؤدي هذا الرمز إلى إزالة القيم المخزنة مؤقتًا بعد كل تحديث أو حذف كيانات النشر. ستكون إبطال قوائم الكيانات ، مثل المقالات الخمسة الأولى أو الأخبار العاجلة ، أكثر تعقيدًا بعض الشيء. رأيت ثلاث استراتيجيات:


لا تقم بتعطيل الإستراتيجية


فقط لا تلمس هذه القيم. عادة ، هذا لا يجلب أي مشاكل. لا بأس أن تظهر الأخبار الجديدة في قائمة الأخير في وقت لاحق (بالطبع ، إذا لم تكن هذه بوابة أخبار كبيرة). لكن بالنسبة لبعض المشاريع ، من المهم حقًا الحصول على بيانات جديدة في هذه القوائم.


البحث ونزع فتيل الاستراتيجية


في كل مرة تقوم فيها بتحديث منشور ، يمكنك محاولة العثور عليه في القوائم المخزنة مؤقتًا ، وإذا كان هناك ، احذف هذه القيمة المخزنة مؤقتًا.


 public function getTopPosts() { return \Cache::remember('top_posts', 900, function() { return Post::/*   top-5*/()->get(); }); } class CheckAndClearTopPostsCache { public function handle(PostEvent $event) { $updatedPost = $event->getPost(); $posts = \Cache::get('top_posts', []); foreach($posts as $post) { if($updatedPost->id == $post->id) { \Cache::forget('top_posts'); return; } } } } 

يبدو قبيحا ، لكنه يعمل.


استراتيجية "معرف المتجر"


إذا كان ترتيب العناصر في القائمة غير مهم ، فيمكن فقط تخزين معرف الإدخالات في ذاكرة التخزين المؤقت. بعد تلقي المعرف ، يمكنك إنشاء قائمة مفاتيح للنموذج 'post_'.$id والحصول على جميع القيم باستخدام Cache::many method ، والتي تحصل على الكثير من القيم من ذاكرة التخزين المؤقت في طلب واحد (وهذا ما يسمى أيضًا multi get).


إبطال ذاكرة التخزين المؤقت ليس عبثًا يُطلق عليه إحدى الصعوبات في البرمجة وهو صعب جدًا في بعض الحالات.


التخزين المؤقت العلاقة


يتطلب التخزين المؤقت للكيانات ذات العلاقات مزيدًا من الاهتمام.


 $post = Post::findOrFail($id); foreach($post->comments...) 

ينفذ هذا الرمز استعلامات SELECT . الحصول على الكيان بواسطة id والتعليقات بواسطة post_id . ننفذ التخزين المؤقت:


 public function getPost($id): Post { return \Cache::remember('post_' . $id, 900, function() use ($id) { return Post::findOrFail($id); }); } $post = getPost($id); foreach($post->comments...) 

تم تخزين الطلب الأول مؤقتًا ، بينما لم يتم تخزين الطلب الثاني. عندما يكتب برنامج تشغيل ذاكرة التخزين المؤقت نشر إلى ذاكرة التخزين المؤقت ، لا يتم تحميل comments بعد. إذا كنا نريد تخزينها مؤقتًا أيضًا ، فعلينا تحميلها يدويًا:


 public function getPost($id): Post { return \Cache::remember('post_' . $id, 900, function() use ($id) { $post = Post::findOrFail($id); $post->load('comments'); return $post; }); } 

يتم الآن تخزين كلا الطلبين مؤقتًا ، ولكن يتعين علينا إبطال قيم 'post_'.$id كل مرة يتم فيها إضافة تعليق. إنه غير فعال للغاية ، لذلك من الأفضل تخزين ذاكرة التخزين المؤقت للتعليق بشكل منفصل:


 public function getPostComments(Post $post) { return \Cache::remember('post_comments_' . $post->id, 900, function() use ($post) { return $post->comments; }); } $post = getPost($id); $comments = getPostComments($post); foreach($comments...) 

في بعض الأحيان ، يرتبط الجوهر والموقف ببعضهما البعض ويتم استخدامهما دائمًا معًا (ترتيب مع التفاصيل ، والنشر مع الترجمة إلى اللغة المطلوبة). في هذه الحالة ، يعد تخزينها في ذاكرة تخزين مؤقت واحدة أمرًا طبيعيًا جدًا.


مصدر واحد للحقيقة لمفاتيح ذاكرة التخزين المؤقت


في حالة تنفيذ المشروع لإلغاء التحقق ، يتم إنشاء مفاتيح ذاكرة التخزين المؤقت في مكانين على الأقل: للاتصال بـ Cache::get / Cache::remember وللاتصال بـ Cache::forget . لقد واجهت بالفعل مواقف عندما تم تغيير هذا المفتاح في مكان واحد ، ولكن ليس في مكان آخر ، وكسر العجز. النصيحة المعتادة لمثل هذه الحالات هي الثوابت ، ولكن يتم إنشاء مفاتيح ذاكرة التخزين المؤقت بشكل حيوي ، لذلك يمكنني استخدام فئات خاصة تنشئ المفاتيح:


 final class CacheKeys { public static function postById($postId): string { return 'post_' . $postId; } public static function postComments($postId): string { return 'post_comments' . $postId; } } \Cache::remember(CacheKeys::postById($id), 900, function() use ($id) { $post = Post::findOrFail($id); }); // .... \Cache::forget(CacheKeys::postById($id)); 

يمكن أيضًا تقديم العمر الافتراضي في ثوابت من أجل تحسين إمكانية القراءة. هذه 900 أو 15 * 60 زيادة الحمل المعرفي عند قراءة التعليمات البرمجية.


لا تستخدم ذاكرة التخزين المؤقت في عمليات الكتابة


عند تنفيذ عمليات الكتابة ، مثل تغيير عنوان أو نص المنشور ، من المغري استخدام طريقة getPost المكتوبة مسبقًا:


 $post = getPost($id); $post->title = $newTitle; $post->save(); 

من فضلك لا تفعل ذلك. قد تكون القيمة في ذاكرة التخزين المؤقت قديمة ، حتى إذا تم الإبطال بشكل صحيح. ستفقد حالة السباق الصغيرة والنشر التغييرات التي أجراها مستخدم آخر. سوف تساعد الأقفال المتفائلة على الأقل في عدم فقد التغييرات ، لكن عدد الطلبات الخاطئة يمكن أن يزيد بشكل كبير.


أفضل حل هو استخدام منطق اختيار كيان مختلف تمامًا لعمليات القراءة والكتابة (مرحبًا ، CQRS). في عمليات الكتابة ، تحتاج دائمًا إلى تحديد أحدث قيمة من قاعدة البيانات. ولا تنس الأقفال (متفائلة أو متشائمة) للبيانات الهامة.


أعتقد أن هذا يكفي لمقال تمهيدي. يعد التخزين المؤقت موضوعًا معقدًا وطويلًا للغاية ، مع مصائد للمطورين ، ولكن مكاسب الأداء تفوق في بعض الأحيان جميع الصعوبات.

Source: https://habr.com/ru/post/ar463495/


All Articles