كيفية استخدام Redis لزيادة فعالية واجهات برمجة تطبيقات الويب الخاصة بك

يعد الأداء معلمة أساسية يجب مراعاتها عند تصميم أي برنامج. إنه مهم بشكل خاص عندما يتعلق الأمر بما يحدث وراء الكواليس.

نحن ، كمطورين وتقنيين ، نعتمد العديد من التعديلات والتطبيقات من أجل تحسين الأداء. هذا هو المكان الذي يأتي دور التخزين المؤقت.

يُعرّف التخزين المؤقت بأنه آلية لتخزين البيانات أو الملفات في موقع تخزين مؤقت حيث يمكن الوصول إليها فورًا عند الحاجة.

أصبح التخزين المؤقت أمرًا ضروريًا في تطبيقات الويب في الوقت الحاضر. يمكننا استخدام Redis لتحسين واجهات برمجة تطبيقات الويب الخاصة بنا - والتي تم إنشاؤها باستخدام Node.js و MongoDB.

Redis: نظرة عامة على الشخص العادي

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

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

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

لنتحدث عن الكود

قبل أن نبدأ ، ستحتاج إلى الحصول على إعداد redis في نظامك المحلي. يمكنك متابعة عملية الإعداد السريعة هذه لبدء تشغيل redis.

منجز؟ رائع. لنبدأ. لدينا تطبيق بسيط تم إنشاؤه في Express والذي يستخدم مثيل في MongoDB Atlas لقراءة البيانات وكتابتها منه.

لدينا اثنين من واجهات برمجة التطبيقات الرئيسية التي تم إنشاؤها في /blogsملف المسار.

... // GET - Fetches all blog posts for required user blogsRouter.route('/:user') .get(async (req, res, next) => { const blogs = await Blog.find({ user: req.params.user }); res.status(200).json({ blogs, }); }); // POST - Creates a new blog post blogsRouter.route('/') .post(async (req, res, next) => { const existingBlog = await Blog.findOne({ title: req.body.title }); if (!existingBlog) { let newBlog = new Blog(req.body); const result = await newBlog.save(); return res.status(200).json({ message: `Blog ${result.id} is successfully created`, result, }); } res.status(200).json({ message: 'Blog with same title exists', }); }); ...

نثر بعض الخير ريديس

نبدأ بتنزيل حزمة npm redisللاتصال بخادم redis المحلي.

const mongoose = require('mongoose'); const redis = require('redis'); const util = require('util'); const redisUrl = 'redis://127.0.0.1:6379'; const client = redis.createClient(redisUrl); client.hget = util.promisify(client.hget); ...

نحن نستخدم utils.promisifyالدالة لتحويل client.hgetالدالة لإرجاع وعد بدلاً من رد نداء. يمكنك قراءة المزيد عنها promisificationهنا.

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

يجب أن تكون إستراتيجيتنا للتخزين المؤقت قادرة على معالجة النقاط التالية.

  • تخزين الطلب لجميع منشورات المدونة مؤقتًا لمستخدم معين
  • امسح ذاكرة التخزين المؤقت في كل مرة يتم فيها إنشاء منشور مدونة جديد

التحديات المحتملة التي يجب أن نتوخى الحذر منها أثناء قيامنا باستراتيجيتنا هي:

  • الطريقة الصحيحة للتعامل مع إنشاء المفتاح لتخزين بيانات ذاكرة التخزين المؤقت
  • منطق انتهاء صلاحية ذاكرة التخزين المؤقت والانتهاء القسري للحفاظ على حداثة ذاكرة التخزين المؤقت
  • التنفيذ القابل لإعادة الاستخدام لمنطق التخزين المؤقت

حسنا. لقد تم تدوين نقاطنا و Redis متصلة. إلى الخطوة التالية.

تجاوز وظيفة Mongoose Exec الافتراضية

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

... const exec = mongoose.Query.prototype.exec; ... mongoose.Query.prototype.exec = async function() { ... const result = await exec.apply(this, arguments); console.log('Data Source: Database'); return result; } ...

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

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

للإشارة إلى الاستعلامات التي يجب أن تكون جاهزة للتخزين المؤقت ، نقوم بإنشاء استعلام نمس. نحن نقدم القدرة على تمرير userلاستخدامه كمفتاح تجزئة من خلال optionsالكائن.

ملاحظة: يعمل Hashkey كمعرّف لهيكل بيانات التجزئة والذي ، من منظور الشخص العادي ، يمكن ذكره كمفتاح أصلي لمجموعة من أزواج القيمة الرئيسية. وبالتالي ، تمكين التخزين المؤقت لعدد أكبر من مجموعة قيم الاستعلام. يمكنك قراءة المزيد عن التجزئة باللون الأحمر هنا.
... mongoose.Query.prototype.cache = function(options = {})  'default'); return this; ; ...

بعد القيام بذلك ، يمكننا بسهولة استخدام cache()الاستعلام مع الاستعلامات التي نريد تخزينها مؤقتًا بالطريقة التالية.

... const blogs = await Blog .find({ user: req.params.user }) .cache({ key: req.params.user }); ...

صياغة منطق ذاكرة التخزين المؤقت

لقد قمنا بإعداد استعلام مشترك قابل لإعادة الاستخدام للإشارة إلى الاستعلامات التي يجب تخزينها مؤقتًا. دعنا نمضي قدمًا ونكتب منطق التخزين المؤقت المركزي.

... mongoose.Query.prototype.exec = async function() { if (!this.enableCache) { console.log('Data Source: Database'); return exec.apply(this, arguments); } const key = JSON.stringify(Object.assign({}, this.getQuery(), { collection: this.mongooseCollection.name, })); const cachedValue = await client.hget(this.hashKey, key); if (cachedValue) { const parsedCache = JSON.parse(cachedValue); console.log('Data Source: Cache'); return Array.isArray(parsedCache) ? parsedCache.map(doc => new this.model(doc)) : new this.model(parsedCache); } const result = await exec.apply(this, arguments); client.hmset(this.hashKey, key, JSON.stringify(result), 'EX', 300); console.log('Data Source: Database'); return result; }; ...

عندما نستخدم cache()الاستعلام جنبًا إلى جنب مع استعلامنا الرئيسي ، نقوم بتعيين enableCacheالمفتاح ليكون صحيحًا.

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

نستخدم collectionالاسم مع الاستعلام الافتراضي كاسم مفتاح من أجل التفرد. مفتاح التجزئة المستخدم هو الاسم userالذي قمنا بتعيينه مسبقًا في cache()تعريف الوظيفة.

يتم جلب البيانات المخزنة مؤقتًا باستخدام client.hget()الوظيفة التي تتطلب مفتاح التجزئة والمفتاح الناتج كمعلمات.

ملاحظة: نستخدم دائمًا JSON.parse()أثناء جلب أي بيانات من redis. وبالمثل ، نستخدم JSON.stringify()المفتاح والبيانات قبل تخزين أي شيء في redis. يتم ذلك لأن redis لا يدعم بنيات بيانات JSON.

بمجرد حصولنا على البيانات المخزنة مؤقتًا ، يتعين علينا تحويل كل كائن من الكائنات المخزنة مؤقتًا إلى نموذج نمس. يمكن القيام بذلك عن طريق استخدام new this.model().

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

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

انتهاء صلاحية ذاكرة التخزين المؤقت القسري

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

للقيام بذلك ، يتعين علينا مسح ذاكرة التخزين المؤقت المتعلقة بهذا المستخدم وتحديثها ببيانات جديدة. لذلك علينا أن نفرض انتهاء الصلاحية. يمكننا القيام بذلك عن طريق استدعاء del()الوظيفة التي يوفرها redis.

... module.exports = { clearCache(hashKey) { console.log('Cache cleaned'); client.del(JSON.stringify(hashKey)); } } ...

We also have to keep in mind that we will be forcing expiration on multiple routes. One extensible way is to use this clearCache() as a middleware and call it once any query related to a route has finished execution.

const { clearCache } = require('../services/cache'); module.exports = async (req, res, next) => { // wait for route handler to finish running await next(); clearCache(req.body.user); } 

This middleware can be easily called on a particular route in the following way.

... blogsRouter.route('/') .post(cleanCache, async (req, res, next) => { ... } ...

And we are done. I agree that was a quite a lot of code. But with that last part, we have set up redis with our application and taken care of almost all the likely challenges. It is time to see our caching strategy in action.

Redis in Action

We make use of Postman as the API client to see our caching strategy in action. Here we go. Let's run through the API operations, one by one.

  1. We create a new blog post using the /blogs route

2. We then fetch all the blog posts related to user tejaz

3. نجلب جميع مشاركات المدونة للمستخدم tejazمرة أخرى.

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

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

يمكنك العثور على الكود المصدري الكامل في redis-expressالمجلد هنا.

tarique93102 / article-snippets Repository الذي يحتوي على تطبيقات النماذج الأولية ومقتطفات التعليمات البرمجية المتعلقة بنشر المفاهيم - tarique93102 / article-snippets tarique93102 GitHub

استنتاج

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

يمكنك العثور على المجموعة الكاملة من أوامر redis هنا. يمكنك استخدامه redis-cliلمراقبة بيانات ذاكرة التخزين المؤقت وعمليات التطبيق.

الإمكانيات التي توفرها أي تقنية معينة لا حصر لها حقًا. إذا كان لديك أي استفسارات ، يمكنك التواصل معي عبر LinkedIn.

في غضون ذلك ، استمر في الترميز.