So implementieren Sie Caching in Node.js mit Redis

Der Autor hat /dev/color ausgewählt, um eine Spende im Rahmen des Write for DOnations-Programms zu erhalten.

Einführung

Die meisten Anwendungen sind auf Daten angewiesen, sei es aus einer Datenbank oder einer API. Daten von einer API abzurufen sendet eine Netzwerkanfrage an den API-Server und liefert die Daten als Antwort zurück. Diese Rundreisen dauern Zeit und können die Reaktionszeit Ihrer Anwendung für Benutzer erhöhen. Darüber hinaus beschränken die meisten APIs die Anzahl der Anfragen, die sie innerhalb eines bestimmten Zeitraums an eine Anwendung bedienen können, ein Vorgang, der als Rate Limiting bekannt ist.

Um diese Probleme zu umgehen, können Sie Ihre Daten zwischenspeichern, sodass die Anwendung eine einzige Anfrage an eine API stellt und alle nachfolgenden Datenanfragen die Daten aus dem Cache abrufen. Redis, eine In-Memory-Datenbank, die Daten im Server-Speicher speichert, ist ein beliebtes Werkzeug zum Zwischenspeichern von Daten. Sie können in Node.js eine Verbindung zu Redis herstellen, indem Sie das node-redis-Modul verwenden, das Ihnen Methoden zum Abrufen und Speichern von Daten in Redis bietet.

In diesem Tutorial werden Sie eine Express-Anwendung erstellen, die Daten von einer RESTful-API mithilfe des axios-Moduls abruft. Als nächstes werden Sie die App ändern, um die von der API abgerufenen Daten in Redis mithilfe des node-redis-Moduls zu speichern. Danach werden Sie die Gültigkeitsdauer des Caches implementieren, damit der Cache nach Ablauf einer bestimmten Zeit abläuft. Schließlich werden Sie das Express-Middleware verwenden, um Daten zu zwischenspeichern.

Voraussetzungen

Um dem Tutorial zu folgen, benötigen Sie:

Schritt 1 — Einrichten des Projekts

In diesem Schritt installieren Sie die für dieses Projekt erforderlichen Abhängigkeiten und starten einen Express-Server. In diesem Tutorial erstellen Sie ein Wiki mit Informationen über verschiedene Arten von Fischen. Wir nennen das Projekt fish_wiki.

Zuerst erstellen Sie das Verzeichnis für das Projekt mit dem Befehl mkdir:

  1. mkdir fish_wiki

Wechseln Sie in das Verzeichnis:

  1. cd fish_wiki

Initialisieren Sie die package.json-Datei mit dem npm-Befehl:

  1. npm init -y

Die Option -y akzeptiert automatisch alle Standardeinstellungen.

Wenn Sie den Befehl npm init ausführen, wird die package.json-Datei in Ihrem Verzeichnis mit dem folgenden Inhalt erstellt:

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" }

Als nächstes werden Sie die folgenden Pakete installieren:

  • express: ein Webserver-Framework für Node.js.
  • axios: ein Node.js HTTP-Client, der hilfreich ist, um API-Aufrufe zu tätigen.
  • node-redis: ein Redis-Client, der es Ihnen ermöglicht, Daten in Redis zu speichern und darauf zuzugreifen.

Um die drei Pakete zusammen zu installieren, geben Sie den folgenden Befehl ein:

  1. npm install express axios redis

Nachdem Sie die Pakete installiert haben, erstellen Sie einen einfachen Express-Server.

Verwenden Sie nano oder den Texteditor Ihrer Wahl, um die Datei server.js zu erstellen und zu öffnen:

  1. nano server.js

In Ihrer server.js-Datei geben Sie den folgenden Code ein, um einen Express-Server zu erstellen:

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}`);
});

Zuerst importieren Sie express in die Datei. In der zweiten Zeile setzen Sie die Variable app als Instanz von express, was Ihnen den Zugriff auf Methoden wie get, post, listen und viele weitere ermöglicht. Dieses Tutorial wird sich auf die Methoden get und listen konzentrieren.

In der nächsten Zeile definieren und weisen Sie der Variable port die Portnummer zu, auf der der Server hören soll. Wenn keine Portnummer in einer Umgebungsvariablen-Datei verfügbar ist, wird Port 3000 als Standard verwendet.

Schließlich rufen Sie über die Variable app die Methode listen() des Moduls express auf, um den Server auf Port 3000 zu starten.

Speichern Sie die Datei und schließen Sie sie.

Führen Sie die Datei server.js mit dem Befehl node aus, um den Server zu starten:

  1. node server.js

Die Konsole gibt eine Nachricht ähnlich wie folgende aus:

Output
App listening on port 3000

Die Ausgabe bestätigt, dass der Server läuft und bereit ist, Anfragen auf Port 3000 zu bedienen. Da Node.js den Server nicht automatisch neu lädt, wenn Dateien geändert werden, stoppen Sie jetzt den Server mit CTRL+C, damit Sie server.js im nächsten Schritt aktualisieren können.

Nachdem Sie die Abhängigkeiten installiert und einen Express-Server erstellt haben, werden Sie Daten von einer RESTful-API abrufen.

Schritt 2 — Abrufen von Daten von einer RESTful-API ohne Zwischenspeicherung

In diesem Schritt bauen Sie auf dem Express-Server aus dem vorherigen Schritt auf, um Daten von einer RESTful-API abzurufen, ohne Zwischenspeicherung zu implementieren. Dies zeigt, was passiert, wenn Daten nicht im Cache gespeichert werden.

Öffnen Sie zunächst die Datei server.js in Ihrem Texteditor:

  1. nano server.js

Anschließend werden Sie Daten von der FishWatch-API abrufen. Die FishWatch-API liefert Informationen über Fischarten.

In deiner server.js Datei definierst du eine Funktion, die API-Daten mit dem folgenden markierten Code abruft:

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}`);
});

In der zweiten Zeile importierst du das axios Modul. Als nächstes definierst du eine asynchrone Funktion fetchApiData(), die species als Parameter annimmt. Um die Funktion asynchron zu machen, setzt du das async Schlüsselwort voran.

Innerhalb der Funktion rufst du die get() Methode des axios Moduls mit dem API-Endpunkt auf, von dem du die Daten abrufen möchtest, was in diesem Beispiel die FishWatch API ist. Da die get() Methode ein Promise implementiert, setzt du das await Schlüsselwort voran, um das Versprechen aufzulösen. Sobald das Versprechen erfüllt ist und Daten von der API zurückgegeben werden, rufst du die console.log() Methode auf. Die console.log() Methode wird eine Nachricht protokollieren, die besagt, dass eine Anfrage an die API gesendet wurde. Schließlich gibst du die Daten von der API zurück.

Als nächstes definierst du eine Express-Route, die GET-Anfragen akzeptiert. In deiner server.js Datei definierst du die Route mit dem folgenden Code:

fish_wiki/server.js
...

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

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

In dem vorherigen Codeblock rufst du die get() Methode des express Moduls auf, die nur auf GET-Anfragen hört. Die Methode nimmt zwei Argumente entgegen:

  • /fish/:species: Das Endpunkt, auf den Express hören wird. Der Endpunkt nimmt einen Routenparameter :species auf, der alles erfasst, was an dieser Stelle in der URL eingegeben wird.
  • getSpeciesData()(noch nicht definiert): Eine Rückruffunktion, die aufgerufen wird, wenn die URL mit dem im ersten Argument angegebenen Endpunkt übereinstimmt.

Jetzt, da die Route definiert ist, geben Sie die getSpeciesData-Rückruffunktion an:

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

Die getSpeciesData-Funktion ist eine asynchrone Handler-Funktion, die als zweites Argument an die get()-Methode des Express-Moduls übergeben wird. Die getSpeciesData()-Funktion nimmt zwei Argumente entgegen: ein Anforderungsobjekt und ein Antwortobjekt. Das Anforderungsobjekt enthält Informationen über den Client, während das Antwortobjekt die Informationen enthält, die von Express an den Client gesendet werden.

Als nächstes fügen Sie den hervorgehobenen Code hinzu, um fetchApiData() aufzurufen und Daten von einer API in der getSpeciesData()-Rückruffunktion abzurufen:

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

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

In der Funktion extrahieren Sie den Wert, der vom Endpunkt erfasst und im req.params-Objekt gespeichert ist, und weisen ihn der Variablen species zu. In der nächsten Zeile definieren Sie die Variable results und setzen sie auf undefined.

Nachdem du das machst, ruf die fetchApiData()-Funktion mit der species-Variable als Argument auf. Der Aufruf der fetchApiData()-Funktion wird mit der await-Syntax versehen, da sie ein Versprechen zurückgibt. Wenn das Versprechen erfüllt ist, gibt es die Daten zurück, die dann der results-Variable zugewiesen werden.

Als nächstes füge den hervorgehobenen Code hinzu, um Laufzeitfehler zu behandeln:

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");
  }
}
...

Du definierst den try/catch-Block, um Laufzeitfehler zu behandeln. Im try-Block rufst du fetchApiData() auf, um Daten von einer API abzurufen.
Wenn ein Fehler auftritt, protokolliert der catch-Block den Fehler und gibt einen 404-Statuscode mit einer „Daten nicht verfügbar“-Antwort zurück.

Die meisten APIs geben einen 404-Statuscode zurück, wenn sie keine Daten für eine bestimmte Abfrage haben, was automatisch dazu führt, dass der catch-Block ausgeführt wird. Die FishWatch-API gibt jedoch einen 200-Statuscode mit einem leeren Array zurück, wenn keine Daten für diese spezifische Abfrage vorhanden sind. Ein 200-Statuscode bedeutet, dass die Anfrage erfolgreich war, daher wird der catch()-Block nie ausgelöst.

Um den catch()-Block auszulösen, musst du überprüfen, ob das Array leer ist, und einen Fehler werfen, wenn die if-Bedingung wahr ist. Wenn die if-Bedingungen false auswerten, kannst du eine Antwort an den Client senden, die die Daten enthält.

Um das zu tun, füge den hervorgehobenen Code hinzu:

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");
  }
}
...

Sobald die Daten von der API zurückgegeben werden, überprüft die if-Anweisung, ob die Variable results leer ist. Wenn die Bedingung erfüllt ist, verwenden Sie die throw-Anweisung, um einen benutzerdefinierten Fehler mit der Meldung API hat ein leeres Array zurückgegeben zu werfen. Nachdem es ausgeführt wurde, wechselt die Ausführung zum catch-Block, der die Fehlermeldung protokolliert und eine Antwort mit dem Statuscode 404 zurückgibt.

Umgekehrt, wenn die Variable results Daten enthält, wird die Bedingung der if-Anweisung nicht erfüllt sein. Als Ergebnis überspringt das Programm den if-Block und führt die Methode send des Antwortobjekts aus, die eine Antwort an den Client sendet.

Die send-Methode akzeptiert ein Objekt mit den folgenden Eigenschaften:

  • fromCache: Die Eigenschaft akzeptiert einen Wert, der Ihnen mitteilt, ob die Daten aus dem Redis-Cache oder der API stammen. Sie haben jetzt den Wert false zugewiesen, da die Daten aus einer API stammen.

  • data: Die Eigenschaft wird der Variable results zugewiesen, die die von der API zurückgegebenen Daten enthält.

An diesem Punkt wird Ihr vollständiger Code wie folgt aussehen:

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}`);
});

Jetzt, da alles an seinem Platz ist, speichern Sie die Datei und beenden Sie sie.

Starten Sie den Express-Server:

  1. node server.js

Die Fishwatch API akzeptiert viele Arten, aber wir werden nur die Fischart red-snapper als Routenparameter auf dem Endpunkt verwenden, den Sie im Verlauf dieses Tutorials testen werden.

Starten Sie jetzt Ihren bevorzugten Webbrowser auf Ihrem lokalen Computer. Navigieren Sie zur URL http://localhost:3000/fish/red-snapper.

Hinweis: Wenn Sie das Tutorial auf einem Remote-Server verfolgen, können Sie die App in Ihrem Browser mithilfe von Portweiterleitung anzeigen.

Während der Node.js-Server noch läuft, öffnen Sie ein weiteres Terminal auf Ihrem lokalen Computer und geben Sie dann folgenden Befehl ein:

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

Nachdem Sie eine Verbindung zum Server hergestellt haben, navigieren Sie auf Ihrem lokalen Computer im Webbrowser zur URL http://localhost:3000/fish/red-snapper.

Sobald die Seite geladen ist, sollten Sie sehen, dass fromCache auf false gesetzt ist.

Jetzt aktualisieren Sie die URL noch dreimal und schauen Sie auf Ihr Terminal. Das Terminal wird „Anfrage an die API gesendet“ so oft protokollieren, wie Sie Ihren Browser aktualisiert haben.

Wenn Sie die URL dreimal nach dem ersten Besuch aktualisiert haben, sieht Ihre Ausgabe so aus:

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

Diese Ausgabe zeigt, dass jedes Mal, wenn Sie den Browser aktualisieren, eine Netzwerkanfrage an den API-Server gesendet wird. Wenn Sie eine Anwendung mit 1000 Benutzern hätten, die denselben Endpunkt aufrufen, wären das 1000 Netzwerkanfragen an die API.

Wenn Sie Caching implementieren, wird die Anfrage an die API nur einmal durchgeführt. Alle nachfolgenden Anfragen erhalten Daten aus dem Cache und verbessern die Leistung Ihrer Anwendung.

Beenden Sie vorerst Ihren Express-Server mit CTRL+C.

Nun, da Sie Daten von einer API anfordern und sie an Benutzer weitergeben können, werden Sie Daten, die von einer API zurückgegeben werden, in Redis zwischenspeichern.

Schritt 3 — Zwischenspeichern von RESTful-API-Anfragen mit Redis

In diesem Abschnitt werden Daten aus der API zwischengespeichert, sodass nur der erste Besuch Ihres App-Endpunkts Daten von einem API-Server anfordert und alle folgenden Anfragen Daten aus dem Redis-Cache abrufen.

Öffnen Sie die Datei server.js:

  1. nano server.js

In Ihrer Datei server.js importieren Sie das Modul node-redis:

fish_wiki/server.js

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

In derselben Datei stellen Sie eine Verbindung zu Redis her, indem Sie das Modul node-redis importieren und den markierten Code hinzufügen:

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) {
  ...
}
...

Zuerst definieren Sie die Variable redisClient mit dem Wert undefined. Danach definieren Sie eine anonyme selbstaufrufende asynchrone Funktion, eine Funktion, die unmittelbar nach ihrer Definition ausgeführt wird. Sie definieren eine anonyme selbstaufrufende asynchrone Funktion, indem Sie eine namenlose Funktionsdefinition in Klammern (async () => {...}) einschließen. Um sie selbstaufrufend zu machen, folgen Sie ihr unmittelbar mit einem weiteren Satz Klammern (), was dann so aussieht: (async () => {...})().

Innerhalb der Funktion rufst du die Methode createClient() des Moduls redis auf, die ein redis-Objekt erstellt. Da du den Port für Redis nicht angegeben hast, verwendet Redis standardmäßig den Port 6379.

Du rufst auch die Node.js-Methode on() auf, die Ereignisse auf dem Redis-Objekt registriert. Die on()-Methode nimmt zwei Argumente entgegen: error und einen Rückruf. Das erste Argument error ist ein Ereignis, das ausgelöst wird, wenn Redis einen Fehler feststellt. Das zweite Argument ist ein Rückruf, der ausgeführt wird, wenn das error-Ereignis ausgelöst wird. Der Rückruf protokolliert den Fehler in der Konsole.

Zuletzt rufst du die Methode connect() auf, die die Verbindung mit Redis auf dem Standardport 6379 startet. Die connect()-Methode gibt ein Versprechen zurück, daher versehen Sie es mit der await-Syntax, um es aufzulösen.

Jetzt, da deine Anwendung mit Redis verbunden ist, wirst du den Rückruf getSpeciesData() ändern, um Daten beim ersten Besuch in Redis zu speichern und die Daten aus dem Cache für alle folgenden Anfragen abzurufen.

Füge in deiner Datei server.js den hervorgehobenen Code hinzu und aktualisiere ihn:

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) {
    ...
  }
}
...

In der Funktion getSpeciesData definieren Sie die Variable isCached mit dem Wert false. Innerhalb des try-Blocks rufen Sie die Methode get() des Moduls node-redis mit species als Argument auf. Wenn die Methode den Schlüssel in Redis findet, der dem Wert der Variablen species entspricht, gibt sie die Daten zurück, die dann der Variablen cacheResults zugewiesen werden.

Als Nächstes überprüft eine if-Anweisung, ob die Variable cacheResults Daten enthält. Wenn die Bedingung erfüllt ist, wird der Variablen isCached der Wert true zugewiesen. Anschließend rufen Sie die Methode parse() des Objekts JSON mit cacheResults als Argument auf. Die Methode parse() konvertiert JSON-String-Daten in ein JavaScript-Objekt. Nachdem das JSON geparst wurde, rufen Sie die Methode send() auf, die ein Objekt entgegennimmt, das die Eigenschaft fromCache auf die Variable isCached setzt. Die Methode sendet die Antwort an den Client.

Wenn die Methode get() des Moduls node-redis keine Daten im Cache findet, wird die Variable cacheResults auf null gesetzt. Infolgedessen ergibt die if-Anweisung false. Wenn das geschieht, springt die Ausführung zum else-Block, in dem Sie die Funktion fetchApiData() aufrufen, um Daten von der API abzurufen. Sobald jedoch die Daten von der API zurückgegeben werden, werden sie nicht in Redis gespeichert.

Um die Daten im Redis-Cache zu speichern, müssen Sie die Methode set() des Moduls node-redis verwenden, um sie zu speichern. Fügen Sie dazu die hervorgehobene Zeile hinzu:

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) {
    ...
  }
}
...

Innerhalb des else-Blocks, sobald die Daten abgerufen wurden, rufen Sie die set()-Methode des Moduls node-redis auf, um die Daten in Redis unter dem Schlüsselnamen des Werts in der Variablen species zu speichern.

Die set()-Methode nimmt zwei Argumente entgegen, die Schlüssel-Wert-Paare sind: species und JSON.stringify(results).

Das erste Argument, species, ist der Schlüssel, unter dem die Daten in Redis gespeichert werden. Denken Sie daran, dass species auf den Wert gesetzt ist, der an den von Ihnen definierten Endpunkt übergeben wird. Wenn Sie beispielsweise /fish/red-snapper besuchen, wird species auf red-snapper gesetzt, was der Schlüssel in Redis sein wird.

Das zweite Argument, JSON.stringify(results), ist der Wert für den Schlüssel. Im zweiten Argument rufen Sie die Methode stringify() von JSON mit der Variablen results als Argument auf, die die Daten enthält, die von der API zurückgegeben wurden. Die Methode konvertiert JSON in einen String; deshalb haben Sie beim Abrufen von Daten aus dem Cache mit der Methode get() des Moduls node-redis zuvor die Methode JSON.parse mit der Variablen cacheResults als Argument aufgerufen.

Ihre vollständige Datei wird nun wie folgt aussehen:

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}`);
});

Speichern und verlassen Sie Ihre Datei und führen Sie die Datei server.js mit dem Befehl node aus:

  1. node server.js

Nachdem der Server gestartet wurde, aktualisieren Sie http://localhost:3000/fish/red-snapper in Ihrem Browser.

Bemerken Sie, dass fromCache immer noch auf false gesetzt ist:

Aktualisieren Sie die Seite erneut, um zu sehen, dass dieses Mal fromCache auf true gesetzt ist:

Aktualisieren Sie die Seite fünfmal und kehren Sie dann zum Terminal zurück. Ihre Ausgabe wird ähnlich wie folgt aussehen:

Output
App listening on port 3000 Request sent to the API

Jetzt wurde Anfrage an die API gesendet nur einmal protokolliert, nachdem die URL mehrmals aktualisiert wurde, im Gegensatz zum letzten Abschnitt, wo die Nachricht bei jeder Aktualisierung protokolliert wurde. Diese Ausgabe bestätigt, dass nur eine Anfrage an den Server gesendet wurde und dass anschließend die Daten aus Redis abgerufen werden.

Um weiter zu bestätigen, dass die Daten in Redis gespeichert sind, stoppen Sie Ihren Server mit CTRL+C. Verbinden Sie mit dem Redis-Server-Client mit dem folgenden Befehl:

  1. redis-cli

Rufen Sie die Daten unter dem Schlüssel red-snapper ab:

  1. get red-snapper

Ihre Ausgabe wird ähnlich wie folgt aussehen (bearbeitet für Kürze):

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

Die Ausgabe zeigt die stringifizierte Version der JSON-Daten, die die API zurückgibt, wenn Sie den Endpunkt /fish/red-snapper besuchen, was bestätigt, dass die API-Daten im Redis-Cache gespeichert sind.

Verlassen Sie den Redis-Server-Client:

  1. exit

Jetzt, da Sie Daten aus einer API zwischenspeichern können, können Sie auch die Gültigkeit des Zwischenspeichers festlegen.

Schritt 4 — Implementierung der Zwischenspeichergültigkeit

Beim Zwischenspeichern von Daten müssen Sie wissen, wie oft sich die Daten ändern. Einige API-Daten ändern sich innerhalb von Minuten, andere innerhalb von Stunden, Wochen, Monaten oder Jahren. Durch Festlegen einer geeigneten Zwischenspeicherungsdauer wird sichergestellt, dass Ihre Anwendung stets aktuelle Daten an Ihre Benutzer liefert.

In diesem Schritt legen Sie die Gültigkeitsdauer des Zwischenspeichers für die API-Daten fest, die in Redis gespeichert werden müssen. Wenn der Cache abläuft, sendet Ihre Anwendung eine Anfrage an die API, um aktuelle Daten abzurufen.

Sie müssen Ihre API-Dokumentation konsultieren, um die richtige Ablaufzeit für den Cache festzulegen. Die meisten Dokumentationen geben an, wie häufig die Daten aktualisiert werden. Es gibt jedoch einige Fälle, in denen die Dokumentation diese Informationen nicht bereitstellt, sodass Sie möglicherweise raten müssen. Überprüfen des last_updated-Eigenschaft verschiedener API-Endpunkte kann anzeigen, wie häufig die Daten aktualisiert werden.

Nachdem Sie die Zwischenspeicherdauer festgelegt haben, müssen Sie sie in Sekunden umrechnen. Zur Demonstration in diesem Tutorial legen Sie die Zwischenspeicherdauer auf 3 Minuten oder 180 Sekunden fest. Diese Beispielzeit erleichtert das Testen der Zwischenspeicherungsfunktionalität.

Um die Gültigkeitsdauer des Zwischenspeichers zu implementieren, öffnen Sie die Datei server.js:

  1. nano server.js

Fügen Sie den markierten Code hinzu:

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}`);
});

In der set()-Methode des node-redis-Moduls übergeben Sie als drittes Argument ein Objekt mit den folgenden Eigenschaften:

  • EX: akzeptiert einen Wert mit der Zwischenspeicherdauer in Sekunden.
  • NX: wenn auf true gesetzt, stellt sicher, dass die set()-Methode nur einen Schlüssel setzt, der noch nicht in Redis existiert.

Speichern Sie Ihre Datei und beenden Sie sie.

Gehen Sie zurück zum Redis-Serverclient, um die Cache-Gültigkeit zu testen:

  1. redis-cli

Löschen Sie den red-snapper-Schlüssel in Redis:

  1. del red-snapper

Beenden Sie den Redis-Client:

  1. exit

Starten Sie jetzt den Entwicklungsserver mit dem Befehl node:

  1. node server.js

Wechseln Sie zurück zu Ihrem Browser und aktualisieren Sie die URL http://localhost:3000/fish/red-snapper. In den nächsten drei Minuten sollte die Ausgabe im Terminal bei Aktualisierung der URL konsistent mit der folgenden Ausgabe sein:

Output
App listening on port 3000 Request sent to the API

Nach Ablauf von drei Minuten aktualisieren Sie die URL in Ihrem Browser. Im Terminal sollten Sie sehen, dass „Anfrage an die API gesendet“ zweimal protokolliert wurde.

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

Diese Ausgabe zeigt, dass der Cache abgelaufen ist und erneut eine Anfrage an die API gestellt wurde.

Sie können den Express-Server stoppen.

Jetzt, da Sie die Cache-Gültigkeit festlegen können, werden Sie als nächstes Daten mit Middleware zwischenspeichern.

Schritt 5 – Daten im Middleware-Cache speichern

In diesem Schritt verwenden Sie das Express-Middleware, um Daten im Cache zu speichern. Middleware ist eine Funktion, die auf das Anfrageobjekt, das Antwortobjekt und einen Rückruf zugreifen kann, der nach ihrer Ausführung ausgeführt werden soll. Die Funktion, die nach der Middleware ausgeführt wird, hat ebenfalls Zugriff auf das Anfrage- und Antwortobjekt. Bei Verwendung von Middleware können Sie das Anfrage- und Antwortobjekt ändern oder früher eine Antwort an den Benutzer zurückgeben.

Um Middleware in Ihrer Anwendung für die Zwischenspeicherung zu verwenden, ändern Sie die getSpeciesData()-Handlerfunktion, um Daten von einer API abzurufen und sie in Redis zu speichern. Sie werden den gesamten Code, der nach Daten in Redis sucht, zur cacheData-Middleware-Funktion verschieben.

Wenn Sie den Endpunkt /fish/:species besuchen, wird die Middleware-Funktion zuerst ausgeführt, um nach Daten im Cache zu suchen. Wenn sie gefunden werden, wird eine Antwort zurückgegeben, und die Funktion getSpeciesData wird nicht ausgeführt. Wenn die Middleware jedoch die Daten nicht im Cache findet, ruft sie die Funktion getSpeciesData auf, um Daten von der API abzurufen und sie in Redis zu speichern.

Zuerst öffnen Sie Ihre server.js:

  1. nano server.js

Als nächstes entfernen Sie den markierten Code:

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");
  }
}
...

In der Funktion getSpeciesData() entfernen Sie den gesamten Code, der nach in Redis gespeicherten Daten sucht. Sie entfernen auch die Variable isCached, da die Funktion getSpeciesData() nur Daten von der API abruft und in Redis speichert.

Nachdem der Code entfernt wurde, setzen Sie fromCache wie unten markiert auf false, damit die Funktion getSpeciesData() wie folgt aussieht:

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");
  }
}
...

Die Funktion getSpeciesData() ruft die Daten von der API ab, speichert sie im Cache und gibt eine Antwort an den Benutzer zurück.

Als nächstes fügen Sie den folgenden Code hinzu, um die Middleware-Funktion zum Zwischenspeichern von Daten in Redis zu definieren:

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) {
...
}
...

Die Middleware-Funktion cacheData() nimmt drei Argumente entgegen: req, res und next. Im try-Block überprüft die Funktion, ob der Wert in der Variablen species Daten gespeichert hat, die unter seinem Schlüssel in Redis abgelegt sind. Wenn die Daten in Redis vorhanden sind, werden sie zurückgegeben und in der Variablen cacheResults gesetzt.

Als nächstes überprüft die if-Anweisung, ob cacheResults Daten enthält. Die Daten werden in die Variable results gespeichert, wenn sie als wahr ausgewertet wird. Danach verwendet die Middleware die send()-Methode, um ein Objekt mit den Eigenschaften fromCache auf true und data auf die Variable results zurückzugeben.

Wenn jedoch die if-Anweisung als falsch ausgewertet wird, wechselt die Ausführung zum else-Block. Innerhalb des else-Blocks wird next() aufgerufen, was die Kontrolle an die nächste Funktion übergibt, die danach ausgeführt werden soll.

Um die Middleware cacheData() die Kontrolle an die Funktion getSpeciesData() übergeben zu lassen, wenn next() aufgerufen wird, aktualisieren Sie die get()-Methode des Moduls express entsprechend:

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

Die get()-Methode nimmt jetzt cacheData als ihr zweites Argument, was die Middleware ist, die nach im Redis zwischengespeicherten Daten sucht und eine Antwort zurückgibt, wenn sie gefunden werden.

Jetzt, wenn Sie den Endpunkt /fish/:species besuchen, wird zuerst cacheData() ausgeführt. Wenn Daten zwischengespeichert sind, wird die Antwort zurückgegeben und der Anfrage-Antwort-Zyklus endet hier. Wenn jedoch keine Daten im Cache gefunden werden, wird getSpeciesData() aufgerufen, um Daten von der API abzurufen, sie im Cache zu speichern und eine Antwort zurückzugeben.

Die vollständige Datei sieht jetzt so aus:

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}`);
});

Speichern und beenden Sie Ihre Datei.

Um den Cache ordnungsgemäß zu testen, können Sie den Schlüssel red-snapper in Redis löschen. Gehen Sie dazu in den Redis-Client:

  1. redis-cli

Entfernen Sie den Schlüssel red-snapper:

  1. del red-snapper

Verlassen Sie den Redis-Client:

  1. exit

Führen Sie jetzt die Datei server.js aus:

  1. node server.js

Sobald der Server gestartet ist, gehen Sie zurück zum Browser und besuchen Sie erneut die http://localhost:3000/fish/red-snapper. Aktualisieren Sie sie mehrmals.

Das Terminal wird die Meldung protokollieren, dass eine Anfrage an die API gesendet wurde. Die Middleware cacheData() wird alle Anfragen für die nächsten drei Minuten bedienen. Ihre Ausgabe wird ähnlich aussehen, wenn Sie die URL innerhalb eines vierminütigen Zeitraums zufällig aktualisieren:

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

Das Verhalten entspricht dem, wie die Anwendung im vorherigen Abschnitt funktionierte.

Sie können jetzt Daten mit Middleware in Redis zwischenspeichern.

Abschluss

In diesem Artikel haben Sie eine Anwendung erstellt, die Daten von einer API abruft und die Daten als Antwort an den Client zurückgibt. Anschließend haben Sie die App so modifiziert, dass sie die API-Antwort beim ersten Besuch im Redis zwischenspeichert und die Daten für alle nachfolgenden Anfragen aus dem Cache abruft. Sie haben die Cache-Dauer dann so geändert, dass sie nach Ablauf einer bestimmten Zeit verfällt, und anschließend Middleware verwendet, um den Cache-Datenabruf zu behandeln.

Als nächsten Schritt können Sie die Dokumentation zu Node Redis erkunden, um mehr über die Funktionen des node-redis-Moduls zu erfahren. Sie können auch die Dokumentationen zu Axios und Express lesen, um einen tieferen Einblick in die in diesem Tutorial behandelten Themen zu erhalten.

Um Ihre Node.js-Kenntnisse weiter auszubauen, sehen Sie sich die Anleitung zur Programmierung in Node.js-Serie an.

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