כיצד ליישם אחסון מטמון ב-Node.js באמצעות Redis

המחבר בחר את /dev/color לקבל תרומה כחלק מתוכנית כתיבה בעבור תרומות.

הקדמה

רוב היישומים תלויים בנתונים, בין אם הם מגיעים ממסד נתונים או מ- API. איסוף נתונים מ- API שולח בקשת רשת לשרת ה- API ומחזיר את הנתונים כתגובה. הינך לוקח זמן ויכול להגביר את זמן התגובה של היישום שלך למשתמשים. בנוסף, רוב ה- API מגבילים את מספר הבקשות שהם יכולים לשרת ליישום בתקופת זמן מסוימת, תהליך שנקרא הגבלת קצב.

כדי להתמודד עם בעיות אלו, ניתן להזין את הנתונים שלך כך שהיישום יבצע בקשה יחידה ל- API, וכל הבקשות לנתונים הבאות יאחזרו את הנתונים מהמטמון. Redis, מסד נתונים בזיכרון שמאחסן נתונים בזיכרון השרת, הוא כלי פופולרי למטמון נתונים. ניתן להתחבר ל- Redis ב- Node.js באמצעות מודול node-redis, שמספק לך שיטות לאחזור ולאחסון של נתונים ב- Redis.

במדריך זה, תצביע באפליקצית Express שמשיגה מידע מ- API RESTful באמצעות מודול axios. לאחר מכן, תשנה את האפליקציה כך שתאחסן את המידע שהוזף מה- API ב- Redis באמצעות המודול node-redis. לאחר מכן, תממש את תקופת התוקף של המטמון כך שהמטמון יפוג אחרי שעברה כמות זמן מסוימת. לבסוף, תשתמש ב- Express middleware כדי לאחסן מידע.

נדרש

כדי למשוך אחר המדריך, יהיה עליך:

שלב 1 — הגדרת הפרויקט

בשלב זה, תתקין את התלויות הדרושות לפרויקט ותפעיל שרת Express. במדריך זה, תיצור ויקי שמכילה מידע על סוגי דגים שונים. נקרא לפרויקט fish_wiki.

תחילה, צור את התיקייה לפרויקט באמצעות פקודת mkdir:

  1. mkdir fish_wiki

עבור לתיקייה:

  1. cd fish_wiki

אתחל את קובץ ה־package.json באמצעות הפקודה npm:

  1. npm init -y

האפשרות -y מקבלת אוטומטית את כל הברירות המחדליות.

כאשר אתה מפעיל את הפקודה npm init, היא תיצור את קובץ ה־package.json בתיקייתך עם התוכן הבא:

Output
Wrote to /home/your_username/<^>fish_wiki<^/package.json: { "name": "fish_wiki", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }

לאחר מכן, תתקין את החבילות הבאות:

  • express: ספריית שרת אינטרנט עבור Node.js.
  • axios: לקוח HTTP של Node.js, שמועיל לביצוע קריאות API.
  • node-redis: לקוח Redis שמאפשר לך לאחסן ולגשת לנתונים ב־Redis.

כדי להתקין את שלושת החבילות ביחד, הכנס את הפקודה הבאה:

  1. npm install express axios redis

לאחר התקנת החבילות, תיצור שרת Express בסיסי.

באמצעות nano או עורך הטקסט שתבחר, צור ופתח את קובץ ה־server.js:

  1. nano server.js

בקובץ ה־server.js שלך, הכנס את הקוד הבא כדי ליצור שרת Express:

fish_wiki/server.js
const express = require("express");

const app = express();
const port = process.env.PORT || 3000;


app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

ראשית, ייבא את express לקובץ. בשורה השנייה, הגדר את משתנה ה־app כמופע של express, שמעניק לך גישה לשיטות כמו get, post, listen, ועוד רבות. המדריך יתמקד בשיטות get ו־listen.

בשורה הבאה, הגדר והקצה את משתנה ה־port למספר הפורט שבו אתה רוצה שהשרת יקשוב. אם אין מספר פורט זמין בקובץ משתנים סביבתיים, הפורט 3000 ישמש כברירת מחדל.

סופסוף, באמצעות משתנה app, אתה מפעיל את שיטת ה־listen() של מודול ה־express כדי להתחיל את השרת על פורט 3000.

שמור וסגור את הקובץ.

הרץ את קובץ ה־server.js באמצעות הפקודה node כדי להתחיל את השרת:

  1. node server.js

המסוף יציג הודעה דומה לזו:

Output
App listening on port 3000

הפלט מאשר כי השרת רץ ומוכן לשרת בקשות על פורט 3000. מכיוון ש־Node.js לא מפעיל את השרת באופן אוטומטי כאשר קבצים משתנים, אתה עתה תעצור את השרת באמצעות CTRL+C כדי שתוכל לעדכן את server.js בשלב הבא.

כאשר תכין את התלויות ותיצור שרת Express, תאסוף נתונים מ־API RESTful.

שלב 2 — איסוף נתונים מ־API RESTful בלי שמירת מטמון

בשלב זה, תבנה על השרת Express מהשלב הקודם כדי לאסוף נתונים מ־API RESTful בלי ליישם מטמון, להדגים מה קורה כאשר הנתונים לא מאוחסנים במטמון.

להתחיל, פתח את קובץ ה־server.js בעורך הטקסט שלך:

  1. nano server.js

בשלב הבא, תאסוף נתונים מ־API של FishWatch. API של FishWatch מחזיר מידע על מיני דגים.

בקובץ server.js, הגדר פונקציה שמבקשת נתוני API עם הקוד המסומן הבא:

fish_wiki/server.js
const express = require("express");
const axios = require("axios");

const app = express();
const port = process.env.PORT || 3000;

async function fetchApiData(species) {
  const apiResponse = await axios.get(
    `https://www.fishwatch.gov/api/species/${species}`
  );
  console.log("Request sent to the API");
  return apiResponse.data;
}

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

בשורה השנייה, אתה מייבא את המודול axios. לאחר מכן, אתה מגדיר פונקציה אסינכרונית בשם fetchApiData(), שמקבלת את species כפרמטר. כדי להפוך את הפונקציה לאסינכרונית, אתה משתמש במילת המפתח async לפני ההגדרה שלה.

בתוך הפונקציה, אתה קורא לשיטת get() של מודול axios עם נקודת הגישה ל-API ממנה ברצונך לאחזר את הנתונים, שהיא ממשק ה-FishWatch בדוגמה זו. מאחר ששיטת get() מיישמת הבטחה, אתה משתמש במילת המפתח await לפתירת ההבטחה. לאחר שההבטחה מפורסמת ונתונים מוחזרים מה-API, אתה קורא לשיטת console.log(). שיטת console.log() תקלות הודעה אומרת כי בוצעה בקשה ל-API. לבסוף, אתה מחזיר את הנתונים מה-API.

בשלב הבא, יישום נתיב Express שמקבל בקשות GET. בקובץ server.js, הגדר את הנתיב באמצעות הקוד הבא:

fish_wiki/server.js
...

app.get("/fish/:species", getSpeciesData);

app.listen(port, () => {
  ...
});

בבלוק הקוד הקודם, אתה מפעיל את שיטת get() של מודול express, שמקשיבה רק לבקשות GET. השיטה מקבלת שני ארגומנטים:

  • /fish/:מין: הנקודת קצה שבה יאזין Express. הנקודת קצה מקבלת פרמטר נתיב :מין שלוכדת כל דבר שנכנס באותה תצורה ב-URL.
  • getSpeciesData()(עדיין לא מוגדר): פונקציית הקריאה בחזרה שתיקרא כאשר ה-URL מתאים לנקודת הקצה שצויינה בפרמטר הראשון.

עכשיו שהנתיב מוגדר, ציינו את פונקציית הקריאה בחזרה getSpeciesData:

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
}
app.get("/fish/:species", getSpeciesData);
...

פונקציית getSpeciesData היא פונקציית עקיפה אשר מועברת לשיטת get() של מודול ה-express כפרמטר שני. פונקציית getSpeciesData() מקבלת שני ארגומנטים: אובייקט בקשה ואובייקט תגובה. אובייקט הבקשה מכיל מידע אודות הלקוח, בעוד שאובייקט התגובה מכיל את המידע שיישלח ללקוח מ-Express.

הבא, הוסיפו את הקוד המודגש כדי לקרוא ל-fetchApiData() כדי לקבל נתונים מ-API בפונקציית הקריאה בחזרה getSpeciesData():

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;

  results = await fetchApiData(species);
}
...

בפונקציה, אתה מחלץ את הערך שנלכד מהנקודת הקצה המאוחסן באובייקט req.params, ואז מקצה אותו למשתנה species. בשורה הבאה, אתה מגדיר את המשתנה results ומגדיר אותו ל-undefined.

לאחר מכן, אתה קורא לפונקציה fetchApiData() עם משתנה ה-species כארגומנט. קריאת הפונקציה fetchApiData() מתחילה עם סמן await מכיוון שהיא מחזירה מובטח. כאשר המובטח מתגמר, הוא מחזיר את הנתונים, שמוקצים למשתנה results.

הבא, הוסף את הקוד המודגש כדי לטפל בשגיאות זמן ריצה:

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;

  try {
    results = await fetchApiData(species);
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}
...

אתה מגדיר את הבלוק try/catch כדי לטפל בשגיאות זמן ריצה. בבלוק try, אתה קורא ל-fetchApiData() כדי לקבל נתונים מ-API.
אם נתקלת בשגיאה, הבלוק catch מפעיל את השגיאה ומחזיר קוד סטטוס 404 עם תגובת "נתונים לא זמינים".

רוב ה-API מחזירים קוד סטטוס 404 כאשר אין להם נתונים עבור שאילתה מסוימת, מה שמפעיל את הבלוק catch באופן אוטומטי. אך API של FishWatch מחזיר קוד סטטוס 200 עם מערך ריק כאשר אין נתונים עבור אותה שאילתה מסוימת. קוד סטטוס 200 אומר שהבקשה הייתה מוצלחת, כך שהבלוק catch() אינו מפעיל.

כדי להפעיל את הבלוק catch(), עליך לבדוק אם המערך ריק ולזרוק שגיאה כאשר התנאי if מערכתי נכון. כאשר תנאי if אינו נכון, ניתן לשלוח תגובה ללקוח המכילה את הנתונים.

על מנת לעשות זאת, הוסף את הקוד המודגש:

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  ...
  try {
    results = await fetchApiData(species);
    if (results.length === 0) {
      throw "API returned an empty array";
    }
    res.send({
      fromCache: false,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}
...

כאשר הנתונים מוחזרים מה- API, הפקודת if בודקת אם המשתנה results ריק. אם התנאי מתקיים, אתה משתמש בפקודת throw כדי לזרוק שגיאה מותאמת אישית עם ההודעה API returned an empty array. לאחר שהיא רצה, הביצוע מועבר לבלוק ה- catch, אשר מקלט את ההודעת שגיאה ומחזיר תגובת 404.

להפך, אם המשתנה results מכיל נתונים, תנאי הפקודה if לא יתקיים. כתוצאה מכך, התוכנית תדלג על בלוק ה- if ותבצע את מתודת ה- send של אובייקט התגובה, אשר שולחת תגובה ללקוח.

מתודת ה- send מקבלת אובייקט שמכיל את המאפיינים הבאים:

  • fromCache: המאפיין מקבל ערך שמסייע לך לדעת האם הנתונים מגיעים ממטמון ה- Redis או מה- API. אתה כעת מענין ערך של false מאחר והנתונים מגיעים מה- API.

  • data: המאפיין מקבל את המשתנה results שמכיל את הנתונים שהוחזרו מה- API.

בנקודה זו, הקוד השלם שלך ייראה כך:

fish_wiki/server.js

const express = require("express");
const axios = require("axios");

const app = express();
const port = process.env.PORT || 3000;

async function fetchApiData(species) {
  const apiResponse = await axios.get(
    `https://www.fishwatch.gov/api/species/${species}`
  );
  console.log("Request sent to the API");
  return apiResponse.data;
}

async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;

  try {
    results = await fetchApiData(species);
    if (results.length === 0) {
      throw "API returned an empty array";
    }
    res.send({
      fromCache: false,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}

app.get("/fish/:species", getSpeciesData);

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

עכשיו שהכל במקום, שמור וצא מהקובץ שלך.

התחל את שרת ה- Express:

  1. node server.js

ה- API של Fishwatch מקבל רבות מיני דגים, אך נשתמש רק במין הדג red-snapper כפרמטר נתיב בנקודת הקצה שתבדוק במהלך ההדרכה הזו.

עכשיו הפעל את דפדפן האינטרנט המועדף עליך על המחשב המקומי שלך. נווט לכתובת ה-URL http://localhost:3000/fish/red-snapper.

הערה: אם אתה עוקב אחר הדרכת השימוש על שרת רחוק, תוכל להציג את היישום בדפדפן שלך באמצעות כיוון פתיחת פורטים.

עם השרת של Node.js פעיל עדיין, פתח טרמינל נוסף על המחשב המקומי שלך, ואז הקלד את הפקודה הבאה:

  1. ssh -L 3000:localhost:3000 your-non-root-user@yourserver-ip

בעת התחברות לשרת, נווט לכתובת http://localhost:3000/fish/red-snapper בדפדפן האינטרנט של המחשב המקומי שלך.

כאשר העמוד נטען, אתה צריך לראות את הערך של fromCache מוגדר ל-false.

עכשיו, רענן את כתובת ה-URL עוד שלוש פעמים ובדוק את הטרמינל שלך. הטרמינל ירשום "בקשה נשלחה לשרת ה- API" כמספר הרענונים שביצעת בדפדפן שלך.

אם רעננת את כתובת ה-URL שלוש פעמים לאחר הביקור הראשוני, הפלט שלך ייראה כמו זה:

Output
App listening on port 3000 Request sent to the API Request sent to the API Request sent to the API Request sent to the API

הפלט הזה מציין כי בקשת רשת נשלחת לשרת ה- API בכל פעם שאתה מרענן את הדפדפן שלך. אם היית לך אפליקציה עם 1000 משתמשים שמגיעים לאותה נקודת קצה, זהו 1000 בקשות רשת שנשלחות ל- API.

כאשר אתה מיישם מטמון, הבקשה ל- API תתבצע רק פעם אחת. כל הבקשות המתוך כך יקבלו נתונים מהמטמון, מעלים את ביצועי היישום שלך.

לעכשיו, עצור את השרת של Express בעזרת CTRL+C.

עכשיו שניתן לך לבקש נתונים מ-API ולשרת אותם למשתמשים, תצפה נתונים שנמסרים מ-API ב-Redis.

שלב 3 — הצפנת בקשות API RESTful באמצעות Redis

בחלק זה, תצפה נתונים מה-API כך שרק בביקור הראשון בקצה האפליקציה שלך יבקש נתונים משרת API, וכל הבקשות הבאות יחפשו נתונים ממטמון Redis.

פתח את קובץ server.js:

  1. nano server.js

בקובץ server.js שלך, ייבא את מודול node-redis:

fish_wiki/server.js

const express = require("express");
const axios = require("axios");
const redis = require("redis");
...

באותו קובץ, תתחבר ל-Redis באמצעות מודול node-redis על ידי הוספת הקוד שמודגש:

fish_wiki/server.js

const express = require("express");
const axios = require("axios");
const redis = require("redis");

const app = express();
const port = process.env.PORT || 3000;

let redisClient;

(async () => {
  redisClient = redis.createClient();

  redisClient.on("error", (error) => console.error(`Error : ${error}`));

  await redisClient.connect();
})();

async function fetchApiData(species) {
  ...
}
...

ראשית, תגדיר את המשתנה redisClient עם הערך המוגדר ל-undefined. לאחר מכן, תגדיר פונקציה אנונימית אוטומטית מוקראת, שהיא פונקציה שמופעלת מייד לאחר ההגדרה שלה. אתה מגדיר פונקציה אנונימית אוטומטית מוקראת על ידי לסגירת הגדרת הפונקציה החסרת שם בסוגריים (async () => {...}). כדי להפוך אותה לאוטומטית מוקראת, אתה ממשיך מייד לאחריה בעוד סט של סוגריים (), שמסתיים בצורה שנראית כך (async () => {...})().

בתוך הפונקציה, אתה קורא לשיטת createClient() של מודול ה-redis המייצרת אובייקט של redis. מכיוון שלא סיפקת את הפורט שבו ישמש Redis כאשר הפעלת את שיטת createClient(), Redis תשתמש בפורט 6379, הפורט הברירת מחדלי.

גם אתה קורא לשיטת on() של Node.js שרושמת אירועים על אובייקט ה-Redis. השיטה on() מקבלת שני ארגומנטים: error וקולבק. הארגומנט הראשון error הוא אירוע שמתבצע כאשר Redis נתקלת בשגיאה. הארגומנט השני הוא קולבק שרץ כאשר האירוע error מתפעל. הקולבק מפעיל את השגיאה בקונסול.

לבסוף, אתה קורא לשיטת connect(), שמתחילה את החיבור עם Redis על הפורט הברירת מחדלי 6379. השיטה connect() מחזירה מובטח, לכן אתה מקדם אותה עם תחביר ה-await כדי לפתור אותו.

עכשיו שהיישום שלך מחובר ל-Redis, תשנה את קולבק ה-getSpeciesData() כדי לאחסן נתונים ב-Redis בביקור הראשוני ולאחזר את הנתונים מהמטמון לכל הבקשות שלאחר מכן.

בקובץ ה-server.js שלך, הוסף ועדכן את הקוד המודגש:

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;
  let isCached = false;

  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      isCached = true;
      results = JSON.parse(cacheResults);
    } else {
      results = await fetchApiData(species);
      if (results.length === 0) {
        throw "API returned an empty array";
     }
    }

    res.send({
      fromCache: isCached,
      data: results,
    });
  } catch (error) {
    ...
  }
}
...

בפונקציה getSpeciesData, אתה מגדיר את המשתנה isCached עם הערך false. בתוך בלוק ה־try, אתה קורא לשיטת get() של מודול ה־node-redis עם species כארגומנט. כאשר השיטה מוצאת את המפתח ב־Redis שמתאים לערך המשתנה species, היא מחזירה את הנתונים, שנמצאים אז במשתנה cacheResults.

לאחר מכן, התנאי if בודק אם יש נתונים במשתנה cacheResults. אם התנאי מתקיים, המשתנה isCache מקבל את הערך true. לאחר מכן אתה קורא לשיטת parse() של אובייקט ה־JSON עם cacheResults כארגומנט. שיטת parse() ממירה מחרוזת JSON לאובייקט JavaScript. לאחר שה־JSON נמורר, אתה קורא לשיטת send(), שמקבלת אובייקט עם המאפיין fromCache שמוגדר למשתנה isCached. השיטה שולחת את התגובה ללקוח.

אם השיטה get() של מודול ה־node-redis לא מוצאת נתונים במטמון, המשתנה cacheResults מוגדר ל־null. כתוצאה מכך, התנאי if מתבצע לא נכון. כאשר זה קורה, הביצוע עובר לבלוק ה־else שבו אתה קורא לפונקציה fetchApiData() כדי לאחזר נתונים מהAPI. אך פעם שהנתונים מוחזרים מהAPI, הם אינם מתווספים ל־Redis.

כדי לאחסן את הנתונים במטמון של Redis, עליך להשתמש בשיטת set() של מודול ה־node-redis כדי לשמור אותם. כדי לעשות זאת, הוסף את השורה שמודגשת:

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;
  let isCached = false;

  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      isCached = true;
      results = JSON.parse(cacheResults);
    } else {
      results = await fetchApiData(species);
      if (results.length === 0) {
        throw "API returned an empty array";
      }
      await redisClient.set(species, JSON.stringify(results));
    }

    res.send({
      fromCache: isCached,
      data: results,
    });
  } catch (error) {
    ...
  }
}
...

בתוך הבלוק else, לאחר שהנתונים נטענו, אתה קורא לשיטת set() של מודול ה-node-redis כדי לשמור את הנתונים ב-Redis תחת שם המפתח של הערך במשתנה species.

השיטה set() מקבלת שני ארגומנטים, שהם זוגות של מפתח-ערך: species ו-JSON.stringify(results).

הארגומנט הראשון, species, הוא המפתח שבו הנתונים יישמרו ב-Redis. זכור ש-species מוגדר לערך שעבר לנקודת הסיום שהגדרת. לדוגמה, כאשר אתה מבקר ב-/fish/red-snapper, species מוגדר ל-red-snapper, שיהיה המפתח ב-Redis.

הארגומנט השני, JSON.stringify(results), הוא הערך עבור המפתח. בארגומנט השני, אתה קורא לשיטת stringify() של JSON עם המשתנה results כארגומנט, שמכיל נתונים שחזרו מה-API. השיטה ממירה JSON למחרוזת; לכן, כאשר אתה יוצא לקבל נתונים מהמטמון באמצעות שיטת get() של מודול ה-node-redis, אתה קורא לשיטת JSON.parse עם המשתנה cacheResults כארגומנט.

הקובץ שלך השלם יראה כך:

fish_wiki/server.js
const express = require("express");
const axios = require("axios");
const redis = require("redis");

const app = express();
const port = process.env.PORT || 3000;

let redisClient;

(async () => {
  redisClient = redis.createClient();

  redisClient.on("error", (error) => console.error(`Error : ${error}`));

  await redisClient.connect();
})();

async function fetchApiData(species) {
  const apiResponse = await axios.get(
    `https://www.fishwatch.gov/api/species/${species}`
  );
  console.log("Request sent to the API");
  return apiResponse.data;
}

async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;
  let isCached = false;

  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      isCached = true;
      results = JSON.parse(cacheResults);
    } else {
      results = await fetchApiData(species);
      if (results.length === 0) {
        throw "API returned an empty array";
      }
      await redisClient.set(species, JSON.stringify(results));
    }

    res.send({
      fromCache: isCached,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}

app.get("/fish/:species", getSpeciesData);

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

שמור וצא מהקובץ שלך, והרץ את הקובץ server.js באמצעות הפקודה node:

  1. node server.js

כאשר השרת מתחיל, רענן את http://localhost:3000/fish/red-snapper בדפדפן שלך.

שים לב שהמשתנה fromCache עדיין מוגדר ל-false:

עכשיו רענן את העמוד שוב כדי לראות שהפעם fromCache מוגדר ל-true:

רענן את העמוד חמישה פעמים וחזור לטרמינל. הפלט שלך יראה דומה לדוגמה הבאה:

Output
App listening on port 3000 Request sent to the API

עכשיו, Request sent to the API נרשמה רק פעם אחת לאחר רענון מרובה של כתובות ה-URL, להבדיל מהקטע הקודם בו הודפסה ההודעה לכל רענון. פלט זה מאשר כי נשלחה רק בקשה אחת לשרת ולאחר מכן, הנתונים מובאים מ-Redis.

כדי לאמת נוסף כי הנתונים מאוחסנים ב-Redis, עצור את השרת שלך באמצעות CTRL+C. התחבר ללקוח שרת ה-Redis בעזרת הפקודה הבאה:

  1. redis-cli

קבל את הנתונים תחת המפתח red-snapper:

  1. get red-snapper

הפלט שלך ידמה לדוגמה הבאה (נערך למטרות קצרת הספר):

Output
"[{\"Fishery Management\":\"<ul>\\n<li><a...3\"}]"

הפלט מציג את גרסת המחרוזת של נתוני JSON שה- API מחזיר כאשר אתה מבקר בנקודת הקצה /fish/red-snapper, מה שמאשר כי הנתונים של ה- API מאוחסנים במטמון של Redis.

צא מלקוח השרת של Redis:

  1. exit

עכשיו שאתה יכול לאחסן נתונים מ- API, תוכל גם להגדיר את תוקפי המטמון.

שלב 4 — מימוש תוקפי המטמון

כאשר אתה מאכסן נתונים במטמון, עליך לדעת כמה פעמים הנתונים משתנים. חלק מנתוני ה- API משתנים בדקות; אחרים בשעות, בשבועות, בחודשים או בשנים. קביעת משך המטמון המתאים מבטיחה שיישקל יישום האפליקציה את הנתונים העדכניים למשתמשים שלך.

בשלב זה, תגדיר את תקפות המטמון עבור הנתונים של ה- API שצריך לאחסן ב- Redis. כאשר המטמון פג, האפליקציה שלך תשלח בקשה ל- API כדי לקבל נתונים עדכניים.

עליך להתייעץ עם מסמכי ה- API שלך כדי להגדיר את זמן התפוגה הנכון עבור המטמון. רוב המסמכים יציינו כמה פעמים הנתונים מתעדכנים. עם זאת, ישנם מקרים בהם המסמכים אינם מספקים את המידע, ולכן עשויים להיות עליך לנחש. בדיקת המאפיין last_updated של נקודות הקצה של ה- API שואפת להראות כמה פעמים הנתונים מתעדכנים.

לאחר שבחרת במשך המטמון, עליך להמיר אותו לשניות. למטרת הדגמה במדריך זה, תגדיר את משך המטמון ל- 3 דקות או 180 שניות. משך הדוגמה הזה יקל על בדיקת פונקציונליות משך המטמון.

כדי ליישם את משך תקפות המטמון, פתח את קובץ ה- server.js:

  1. nano server.js

הוסף את הקוד המודגש:

fish_wiki/server.js
const express = require("express");
const axios = require("axios");
const redis = require("redis");

const app = express();
const port = process.env.PORT || 3000;

let redisClient;

(async () => {
  ...
})();

async function fetchApiData(species) {
  ...
}

async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;
  let isCached = false;

  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      isCached = true;
      results = JSON.parse(cacheResults);
    } else {
      results = await fetchApiData(species);
      if (results.length === 0) {
        throw "API returned an empty array";
      }
      await redisClient.set(species, JSON.stringify(results), {
        EX: 180,
        NX: true,
      });
    }

    res.send({
      fromCache: isCached,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}

app.get("/fish/:species", getSpeciesData);

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

בשיטת set() של מודול ה- node-redis, אתה מעביר ארגומנט שלישי של אובייקט עם התכונות הבאות:

  • EX: מקבל ערך עם משך המטמון בשניות.
  • NX: כאשר מוגדר כ- true, זה מבטיח שהשיטה set() תקבע רק מפתח שטרם קיים ב- Redis.

שמור וצא מהקובץ שלך.

תחזור אל לקוח שרת ה־Redis כדי לבדוק את תוקפות המטמון:

  1. redis-cli

מחק את המפתח red-snapper ב־Redis:

  1. del red-snapper

צא מלקוח ה־Redis:

  1. exit

עכשיו, התחל את שרת הפיתוח עם פקודת node:

  1. node server.js

חזור לדפדפן שלך ורענן את כתובת ה־URL http://localhost:3000/fish/red-snapper. למשך שלוש דקות הבאות, אם תרענן את כתובת ה־URL, הפלט בטרמינל צריך להיות תואם לפלט הבא:

Output
App listening on port 3000 Request sent to the API

לאחר שעברו שלוש דקות, רענן את כתובת ה־URL בדפדפן שלך. בטרמינל, תראה שנרשמה פעמיים הודעה "Request sent to the API".

Output
App listening on port 3000 Request sent to the API Request sent to the API

הפלט הזה מראה שתוקף המטמון פג, והתבצעה בקשה אל ה־API שוב.

תוכל לעצור את שרת Express.

עכשיו שאתה יכול להגדיר את תוקף המטמון, תצטרך להשתמש במידלוואר למטמון הנתונים למשך הלאה.

שלב 5 — מטמון נתונים במידלוואר

בשלב זה, תשתמש במידלוואר של Express כדי למטמן נתונים. מידלוואר הוא פונקציה שיש לה גישה לאובייקט הבקשה, אובייקט התגובה, ולקריאת חזרה שצריך להרוץ לאחר שהיא מתבצעת. הפונקציה שרץ אחרי המידלוואר גם יש לה גישה לאובייקטי הבקשה והתגובה. בעת שימוש במידלוואר, תוכל לשנות את אובייקטי הבקשה והתגובה או להחזיר תגובה למשתמש במוקדם יותר.

כדי להשתמש ב- middleware באפליקציה שלך לצורך מטמון, תשנה את פונקציית הטיפול getSpeciesData() כדי לאחזר נתונים מ- API ולאחסן אותם ב- Redis. תעביר את כל הקוד שמחפש נתונים ב- Redis לפונקציית ה- middleware cacheData.

כאשר אתה מבקר בנקודת הסיום /fish/:species, הפונקציית ה- middleware תרוץ ​​ראשונה כדי לחפש נתונים במטמון; אם יימצאו, תחזיר תגובה, והפונקציה getSpeciesData לא תרוץ. אולם, אם ה- middleware לא מוצא את הנתונים במטמון, הוא יקרא לפונקציית getSpeciesData כדי לאחזר נתונים מ- API ולאחסן אותם ב- Redis.

תחליף, פתח את הקובץ server.js:

  1. nano server.js

הבא, הסר את הקוד המודגש:

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;
  let isCached = false;

  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      isCached = true;
      results = JSON.parse(cacheResults);
    } else {
      results = await fetchApiData(species);
      if (results.length === 0) {
        throw "API returned an empty array";
      }
      await redisClient.set(species, JSON.stringify(results), {
        EX: 180,
        NX: true,
      });
    }

    res.send({
      fromCache: isCached,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}
...

בפונקציית getSpeciesData(), הסר את כל הקוד שמחפש נתונים שמאוחסנים ב- Redis. תסיר גם את המשתנה isCached מאחר והפונקציה getSpeciesData() תחזיר נתונים רק מה- API ותאחסן אותם ב- Redis.

לאחר שהקוד הוסר, הגדר את fromCache ל- false כפי שמודגש למטה, כך שהפונקציה getSpeciesData() תיראה כמו הבאה:

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;

  try {
    results = await fetchApiData(species);
    if (results.length === 0) {
      throw "API returned an empty array";
    }
    await redisClient.set(species, JSON.stringify(results), {
      EX: 180,
      NX: true,
    });

    res.send({
      fromCache: false,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}
...

פונקציית getSpeciesData() משיגה את הנתונים מה- API, מאחסנת אותם במטמון, ומחזירה תגובה למשתמש.

הבא, הוסף את הקוד הבא כדי להגדיר את פונקציית ה- middleware לאחסון נתונים ב- Redis:

fish_wiki/server.js
...
async function cacheData(req, res, next) {
  const species = req.params.species;
  let results;
  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      results = JSON.parse(cacheResults);
      res.send({
        fromCache: true,
        data: results,
      });
    } else {
      next();
    }
  } catch (error) {
    console.error(error);
    res.status(404);
  }
}

async function getSpeciesData(req, res) {
...
}
...

הפונקציה cacheData() של middleware מקבלת שלושה ארגומנטים: req, res, ו־next. בתוך בלוק ה־try, הפונקציה בודקת אם הערך במשתנה species מכיל נתונים שמאוחסנים ב־Redis תחת המפתח שלו. אם הנתונים נמצאים ב־Redis, הם מוחזרים ומוגדרים במשתנה cacheResults.

לאחר מכן, ההצהרה של if בודקת אם ישנם נתונים ב־cacheResults. הנתונים נשמרים במשתנה results אם הביטוי מתקיים. לאחר מכן, ה־middleware משתמש בשיטת send() כדי להחזיר אובייקט עם המאפיינים fromCache שמוגדר ל־true ו־data שמוגדר למשתנה results.

אם הביטוי של התנאי if אינו מתקיים, הביצוע מועבר לתוך בלוק ה־else. בתוך בלוק ה־else, אתה קורא ל־next(), שמעביר בקרה לפונקציה הבאה שצריך להתבצע אחריה.

כדי לאפשר ל־cacheData() של middleware להעביר בקרה לפונקציה getSpeciesData() כאשר next() נקרא, יש לעדכן את שיטת ה־get() של מודול ה־express בהתאם:

fish_wiki/server.js
...
app.get("/fish/:species", cacheData, getSpeciesData);
...

שיטת ה־get() מקבלת כעת את cacheData כארגומנט השני שלה, שהוא ה־middleware שמחפש נתונים שמאוחסנים ב־Redis ומחזיר תגובה כאשר מצויים.

עכשיו, כאשר אתה בוקר את נקודת הקצה /fish/:species, cacheData() מופעל תחילה. אם הנתונים מוקשרים במטמון, היא תחזיר את התגובה, ומחזור הבקשה-תגובה מסתיים כאן. אך, אם לא נמצאים נתונים במטמון, תיקרא הפונקציה getSpeciesData() כדי לאחזר נתונים מה-API, לאחסן אותם במטמון, ולהחזיר תגובה.

הקובץ המלא יראה כך כעת:

fish_wiki/server.js

const express = require("express");
const axios = require("axios");
const redis = require("redis");

const app = express();
const port = process.env.PORT || 3000;

let redisClient;

(async () => {
  redisClient = redis.createClient();

  redisClient.on("error", (error) => console.error(`Error : ${error}`));

  await redisClient.connect();
})();

async function fetchApiData(species) {
  const apiResponse = await axios.get(
    `https://www.fishwatch.gov/api/species/${species}`
  );
  console.log("Request sent to the API");
  return apiResponse.data;
}

async function cacheData(req, res, next) {
  const species = req.params.species;
  let results;
  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      results = JSON.parse(cacheResults);
      res.send({
        fromCache: true,
        data: results,
      });
    } else {
      next();
    }
  } catch (error) {
    console.error(error);
    res.status(404);
  }
}
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;

  try {
    results = await fetchApiData(species);
    if (results.length === 0) {
      throw "API returned an empty array";
    }
    await redisClient.set(species, JSON.stringify(results), {
      EX: 180,
      NX: true,
    });

    res.send({
      fromCache: false,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}

app.get("/fish/:species", cacheData, getSpeciesData);

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

שמור וצא מהקובץ שלך.

כדי לבדוק את המטמון בצורה תקינה, תוכל למחוק את המפתח red-snapper ב-Redis. לעשות זאת, עבור אל לקוח Redis:

  1. redis-cli

הסר את המפתח red-snapper:

  1. del red-snapper

צא מלקוח Redis:

  1. exit

כעת, הפעל את קובץ ה-server.js:

  1. node server.js

כאשר השרת מתחיל, חזור לדפדפן ובקר בכתובת http://localhost:3000/fish/red-snapper שוב. רענן את זה מספר פעמים.

הטרמינל תירשם את הודעת שנשלחה בקשה ל-API. קובץ ה-cacheData() ישרת את כל הבקשות לשלוש דקות הבאות. הפלט שלך יראה דומה לזה אם תרענן את ה-URL בתקופת זמן של ארבע דקות:

Output
App listening on port 3000 Request sent to the API Request sent to the API

ההתנהגות עקבית עם איך שהיישום פעל בקטע הקודם.

עכשיו אתה יכול לשמור נתונים במטמון ב-Redis באמצעות מתווך.

מסקנה

במאמר זה, בנית אפליקציה שמביאה נתונים מ- API ומחזירה את הנתונים כתגובה ללקוח. לאחר מכן שינית את האפליקציה כך שהיא תאחסן את תגובת ה- API ב- Redis בביקור הראשון ותשרת את הנתונים מהמטמון לכל בקשה עתידית. שינית את משך הזמן של המטמון כדי שיפוג אחרי כמות זמן מסוימת שחלפה, ואז השתמשת ב- middleware כדי לטפל באחזור נתוני המטמון.

כשלב הבא, נוכל לחקור את תיעוד ה- Node Redis כדי ללמוד עוד על התכונות הזמינות במודול ה- node-redis. תוכל גם לקרוא את תיעוד ה- Axios וה- Express כדי לקבל מבט עמוק יותר על הנושאים שנ covered במדריך זה.

כדי להמשיך ולשפר את מיומנויות התכנות שלך ב- Node.js, ראה את סדרת התכנות How To Code in Node.js.

Source:
https://www.digitalocean.com/community/tutorials/how-to-implement-caching-in-node-js-using-redis