Како се Редис користи за надопуњавање веб АПИ-ја

Перформансе су кључни параметар који треба узети у обзир приликом дизајнирања било ког дела софтвера. Нарочито је важно када је реч о ономе што се дешава иза кулиса.

Ми, као програмери и технолози, усвајамо више подешавања и примена како бисмо побољшали перформансе. Овде долази до изражаја кеширање.

Кеширање је дефинисано као механизам за складиштење података или датотека на месту привременог складишта одакле му се може одмах приступити кад год је то потребно.

Кеширање је данас неопходно у веб апликацијама. Редис можемо користити за допуњавање наших веб АПИ-ја - који су направљени помоћу Ноде.јс и МонгоДБ.

Редис: Лаички преглед

Редис је, према службеној документацији, дефинисан као складиште података у меморији које се користи као база података, посредник порука или кеш меморија. Подржава структуре података као што су низови, хешеви, листе, скупови, сортирани скупови са упитима опсега, битмапе, хиперлоглози, геопросторни индекси са упитима радијуса и токовима.

Ок, то је прилично пуно структура података управо тамо. Да би то било једноставно, готово све подржане структуре података могу се сажети у један или други облик низа. Добићете више јасности док пролазимо кроз имплементацију.

Али једно је јасно. Редис је моћан и ако се правилно користи, наше апликације могу бити не само брже већ и невероватно ефикасне. Доста приче. Упрљајмо руке.

Хајде да разговарамо са кодом

Пре него што започнемо, мораћете да подесите редис у вашем локалном систему. Можете да следите овај поступак брзог подешавања да бисте покренули редис.

Готово? Хладан. Почнимо. Имамо једноставну апликацију створену у Екпрессу која користи инстанцу у МонгоДБ Атлас за читање и писање података.

У /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', }); }); ...

Прскати мало Редисове доброте

Почињемо са преузимањем нпм пакета 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овде.

Веза са Редисом је успостављена. Пре него што почнемо да пишемо било који кеширајући код, вратимо се корак уназад и покушајмо да схватимо који су захтеви које морамо испунити и који су вероватни изазови са којима бисмо се могли суочити.

Наша стратегија кеширања треба да буде у стању да одговори на следеће тачке.

  • Кеширајте захтев за све постове на блогу за одређеног корисника
  • Обришите кеш меморију сваки пут када се креира нови пост на блогу

Вероватни изазови на које бисмо требали бити опрезни док се бавимо стратегијом су:

  • Прави начин за руковање стварањем кључа за чување кеш података
  • Логика истека кеш меморије и принудни истек ради одржавања свежине кеш меморије
  • Вишекратна примена логике кеширања

У реду. Наше тачке су забележене и повезане са редисом. На следећи корак.

Замена задате функције Монгоосе Екец

Желимо да се наша логика кеширања може поново користити. И не само за поновну употребу, ми такође желимо да то буде прва контролна тачка пре него што упутимо било какав упит у базу података. То се лако може урадити једноставним хаковањем свињске копије на функцију екец монгоосе.

... 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објекат проследи да се користи као хеш-кључ .

Напомена: Хасхкеи служи као идентификатор структуре хеш података који се, лаички речено, може навести као надређени кључ скупа парова кључ / вредност. На тај начин, омогућава кеширање већег броја скупа вредности упита. Више о хешовима у редису можете прочитати овде.
... 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()док дохваћамо податке из редис-а. И слично томе, користимо JSON.stringify()кључ и податке пре него што било шта спремимо у редис. То је учињено с обзиром да редис не подржава ЈСОН структуре података.

Након што добијемо кеширане податке, сваки од кешираних објеката морамо трансформисати у мунгос модел. То се може учинити једноставним коришћењем new this.model().

If the cache does not contain the required data, we make a query to the database. Then, having returned the data to the API, we refresh the cache using client.hmset(). We also set a default cache expiration time of 300 seconds. This is customizable based on your caching strategy.

The caching logic is in place. We have also set a default expiration time. Next up, we look at forcing cache expiration whenever a new blog post is created.

Forced Cache Expiration

In certain cases, such as when a user creates a new blog post, the user expects that the new post should be available when they fetche all the posts.

In order to do so, we have to clear the cache related to that user and update it with new data. So we have to force expiration. We can do that by invoking the del() function provided by 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директоријуму овде.

тарикуе93102 / артицле-сниппетс Спремиште које садржи прототипове апликација и исечке кода који се односе на ширење концепата - тарикуе93102 / артицле-сниппетс тарикуе93102 ГитХуб

Закључак

Кеширање је обавезан корак за било коју апликацију ефикасну у перформансама и интензивну употребу података. Редис вам помаже да то лако постигнете у својим веб апликацијама. То је изузетно моћан алат и ако се правилно користи, дефинитивно може пружити изврсно искуство програмерима, као и корисницима око себе.

Комплетан скуп команди редис можете пронаћи овде. Помоћу ње можете redis-cliда надгледате податке кеш меморије и процесе апликације.

Могућности које нуди било која одређена технологија заиста су бескрајне. Ако имате било каквих питања, можете ме контактирати на LinkedIn.

У међувремену наставите са кодирањем.