如何使用 Redis 在 Node.js 中实施缓存

作者选择了 /dev/color以作为 Write for Donations计划的一部分获得捐款。

介绍

大多数应用程序都依赖数据,无论是来自数据库还是API。从API中获取数据会将网络请求发送到API服务器,并作为响应返回数据。这些回程需要时间,可以增加应用程序对用户的响应时间。

为了解决这些问题,您可以缓存数据,以便应用程序向 API 发出单个请求,所有后续的数据请求将从缓存中获取数据。 Redis,一个存储数据在服务器内存储的内存数据库,是一个流行的工具来缓存数据。

在本教程中,您将构建一个从 RESTful API 获取数据的 Express应用程序,使用 axios模块。接下来,您将更改应用程序以使用 'node-redis' 模块在 Redis 中存储从 API 获取的数据。

前提条件

要遵循教程,你需要:

步骤1 - 设置项目

在此步骤中,您将安装该项目所需的依赖,并启动 Express 服务器. 在本教程中,您将创建一个包含不同类型的鱼类信息的wiki。

首先,使用mkdir命令创建项目目录:

1mkdir fish_wiki

移动到目录:

1cd fish_wiki

使用npm命令初始化package.json文件:

1npm init -y

-y 选项会自动接受所有默认值。

当您运行npm init命令时,它将在您的目录中创建package.json文件,其中包含以下内容:

 1[secondary_label Output]
 2Wrote to /home/your_username/fish_wiki<^/package.json:
 3
 4{
 5  "name": "fish_wiki",
 6  "version": "1.0.0",
 7  "description": "",
 8  "main": "index.js",
 9  "scripts": {
10    "test": "echo \"Error: no test specified\" && exit 1"
11  },
12  "keywords": [],
13  "author": "",
14  "license": "ISC"
15}

接下来,您将安装以下软件包:

  • express: Node.js 的 Web 服务器框架.
  • axios: Node.js 的 HTTP 客户端,可用于 API 调用。
  • node-redis:允许您在 Redis 中存储和访问数据的 Redis 客户端。

要一起安装这三个包,请输入以下命令:

1npm install express axios redis

安装包后,您将创建一个基本的Express服务器。

使用nano或您选择的文本编辑器,创建并打开server.js文件:

1nano server.js

server.js文件中,输入以下代码来创建 Express 服务器:

1[label fish_wiki/server.js]
2const express = require("express");
3
4const app = express();
5const port = process.env.PORT || 3000;
6
7app.listen(port, () => {
8  console.log(`App listening on port ${port}`);
9});

在第二行中,您将app变量设置为express的实例,这为您提供了get,post,listen等方法的访问权限。

在下一行中,您定义并将端口变量分配给您希望服务器聆听的端口号,如果环境变量文件中没有端口号,则将使用3000端口作为默认值。

最后,使用app变量,您调用express模块的listen()方法来启动服务器的端口3000

保存并关闭文件。

使用node命令运行server.js文件来启动服务器:

1node server.js

控制台将记录类似于以下的消息:

1[secondary_label Output]
2App listening on port 3000

由于 Node.js 不会在文件更改时自动重新加载服务器,您现在将使用 CTRL + C 停止服务器,以便在下一步更新 server.js。

一旦您安装了依赖性并创建了Express服务器,您将从RESTful API中获取数据。

步骤 2 – 从 RESTful API 获取数据而无需缓存

在此步骤中,您将基于前一步的 Express 服务器来从 RESTful API 获取数据,而无需实现缓存,从而展示数据未存储在缓存中时会发生什么。

首先,在文本编辑器中打开server.js文件:

1nano server.js

接下来,您将从 FishWatch API 获取数据,而 FishWatch API 将返回有关鱼类的信息。

server.js文件中,定义一个函数以以下突出代码请求API数据:

 1[label fish_wiki/server.js]
 2const express = require("express");
 3const axios = require("axios");
 4
 5const app = express();
 6const port = process.env.PORT || 3000;
 7
 8async function fetchApiData(species) {
 9  const apiResponse = await axios.get(
10    `https://www.fishwatch.gov/api/species/${species}`
11  );
12  console.log("Request sent to the API");
13  return apiResponse.data;
14}
15
16app.listen(port, () => {
17  console.log(`App listening on port ${port}`);
18});

在第二行中,您导入轴心模块,然后您定义一个非同步函数fetchApiData(),该函数将物种作为参数。

在函数中,你将axios模块的get()方法调用到你想要的方法来获取数据的 API 终端点,即本示例中的 FishWatch API。由于get()方法实现了 promise,你将它用await关键字预先调用,以解决承诺。一旦承诺得到解决并从 API 返回数据,你会调用console.log()方法。

接下来,您将定义一个接受GET请求的快递路线. 在您的server.js文件中,用以下代码定义该路线:

1[label fish_wiki/server.js]
2...
3
4app.get("/fish/:species", getSpeciesData);
5
6app.listen(port, () => {
7  ...
8});

在前一个代码块中,你呼吁express模块的get()方法,它只听取GET请求。

  • /fish/:species: Express 将收听的终端点. 终端点采用路由参数 :species,捕捉了 URL 中的那个位置中输入的任何内容。

现在路线已定义,指定getSpeciesData回调函数:

1[label fish_wiki/server.js]
2...
3async function getSpeciesData(req, res) {
4}
5app.get("/fish/:species", getSpeciesData);
6...

getSpeciesData函数是向express模块的get()方法传递的非同步处理函数,作为第二个参数。 getSpeciesData()函数需要两个参数:一个 请求对象和一个 响应对象

接下来,添加突出的代码来调用fetchApiData(),以便在getSpeciesData()调用函数中从API中获取数据:

1[label fish_wiki/server.js]
2...
3async function getSpeciesData(req, res) {
4  const species = req.params.species;
5  let results;
6
7  results = await fetchApiData(species);
8}
9...

在函数中,您从存储在req.params对象中的终点中提取捕获的值,然后将其分配给品种变量。

然后,您将用物种变量作为参数调用fetchApiData()函数,然后将fetchApiData()函数调用预先调用等待语法,因为它返回了一个承诺。

接下来,添加突出的代码来处理运行时错误:

 1[label fish_wiki/server.js]
 2...
 3async function getSpeciesData(req, res) {
 4  const species = req.params.species;
 5  let results;
 6
 7  try {
 8    results = await fetchApiData(species);
 9  } catch (error) {
10    console.error(error);
11    res.status(404).send("Data unavailable");
12  }
13}
14...

您定义了 try/catch块来处理运行时错误. 在 try块中,您呼叫fetchApiData()来从API中获取数据 如果遇到错误,则catch块会记录错误并返回一个404状态代码,并响应数据不可用

大多数 API 返回一个 404 状态代码,当它们没有特定查询的数据时,这会自动触发捕获块来执行。 但是,FishWatch API 返回 200 状态代码,当没有该特定查询的数据时,返回一个空数。

要触发catch()块,您需要检查数组是否空,并在如果条件评估为 true 时发出错误。

要做到这一点,添加突出的代码:

 1[label fish_wiki/server.js]
 2...
 3async function getSpeciesData(req, res) {
 4  ...
 5  try {
 6    results = await fetchApiData(species);
 7    if (results.length === 0) {
 8      throw "API returned an empty array";
 9    }
10    res.send({
11      fromCache: false,
12      data: results,
13    });
14  } catch (error) {
15    console.error(error);
16    res.status(404).send("Data unavailable");
17  }
18}
19...

一旦从 API 返回数据,如果声明会检查结果变量是否为空. 如果满足条件,则使用声明将API 返回一个空数组的定制错误。

相反,如果结果变量有数据,则如果语句条件不会满足,因此,程序会跳过如果块并执行响应对象的发送方法,该方法会向客户端发送响应。

发送方法采用具有以下属性的对象:

  • fromCache:属性接受一个值,帮助您知道数据是否来自 Redis 缓存或 API. 您现在分配了一个 false 值,因为数据来自 API.
  • data:属性分配给包含 API 返回数据的 results 变量。

此时,您的完整代码将看起来像这样:

 1[label fish_wiki/server.js]
 2
 3const express = require("express");
 4const axios = require("axios");
 5
 6const app = express();
 7const port = process.env.PORT || 3000;
 8
 9async function fetchApiData(species) {
10  const apiResponse = await axios.get(
11    `https://www.fishwatch.gov/api/species/${species}`
12  );
13  console.log("Request sent to the API");
14  return apiResponse.data;
15}
16
17async function getSpeciesData(req, res) {
18  const species = req.params.species;
19  let results;
20
21  try {
22    results = await fetchApiData(species);
23    if (results.length === 0) {
24      throw "API returned an empty array";
25    }
26    res.send({
27      fromCache: false,
28      data: results,
29    });
30  } catch (error) {
31    console.error(error);
32    res.status(404).send("Data unavailable");
33  }
34}
35
36app.get("/fish/:species", getSpeciesData);
37
38app.listen(port, () => {
39  console.log(`App listening on port ${port}`);
40});

现在一切都已经完成了,保存并退出您的文件。

启动快递服务器:

1node server.js

Fishwatch API 接受了许多物种,但我们只将使用红鱼物种作为终点的路线参数,您将在本教程中测试。

现在,在您的本地计算机上启动您最喜欢的网页浏览器。 导航到 http://localhost:3000/fish/red-snapper URL。

<$>[注] 注: 如果您在远程服务器上遵循教程,则可以使用端口转发在浏览器中查看应用程序。

如果 Node.js 服务器仍在运行,请在本地计算机上打开另一台终端,然后输入以下命令:

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

当您连接到服务器时,请在本地计算机网页浏览器上导航到http://localhost:3000/fish/red-snapper

一旦页面加载,你应该看到fromCache设置为false

Page loaded without cache

现在,再更新URL三次,再看看您的终端,终端将登录向 API 发送请求等您更新浏览器的次数。

如果您在初次访问后三次更新 URL,则输出将如下:

1[secondary_label Output]
2
3App listening on port 3000
4Request sent to the API
5Request sent to the API
6Request sent to the API
7Request sent to the API

此输出显示,每次更新浏览器时都会发送网络请求到API服务器. 如果您有一个应用程序,1000个用户打到相同的端点,那就是1000个网络请求发送到API。

当您实现缓存时,对API的请求只会进行一次,所有后续请求都会从缓存中获取数据,从而提高应用程序的性能。

现在,停止您的Express服务器使用CTRL+C

现在,您可以从 API 请求数据并为用户提供服务,您将在 Redis 中缓存从 API 返回的数据。

步骤 3 — 使用 Redis 缓存 RESTful API 请求

在本节中,您将从 API 缓存数据,以便只有首次访问您的应用终端点才会从 API 服务器中请求数据,所有下面的请求都会从 Redis 缓存中获取数据。

打开server.js文件:

1nano server.js

server.js文件中,导入node-redis模块:

1[label fish_wiki/server.js]
2
3const express = require("express");
4const axios = require("axios");
5const redis = require("redis");
6...

在同一文件中,使用node-redis模块连接到Redis,通过添加突出的代码:

 1[label fish_wiki/server.js]
 2
 3const express = require("express");
 4const axios = require("axios");
 5const redis = require("redis");
 6
 7const app = express();
 8const port = process.env.PORT || 3000;
 9
10let redisClient;
11
12(async () => {
13  redisClient = redis.createClient();
14
15  redisClient.on("error", (error) => console.error(`Error : ${error}`));
16
17  await redisClient.connect();
18})();
19
20async function fetchApiData(species) {
21  ...
22}
23...

首先,您定义了redisClient变量,该值设置为 undefined。之后,您定义了一个匿名自召非同步函数,这是一个在定义后立即运行的函数。您定义了一个匿名自召非同步函数,将无名函数定义包含在对面 (async () => {...}). 为了使其自召,您立即跟随它,另一个对面 (),最终看起来像 (async () => {...})()

在该函数中,您呼吁redis模块的createClient()方法创建一个redis对象.由于您在呼吁createClient()方法时没有提供用于Redis的端口,因此Redis将使用6379端口,即默认端口。

您还将 Node.js 命名为on()方法,该方法记录了 Redis 对象上的事件。on()方法使用两个参数:errorcallback。第一个参数error是当 Redis 遇到错误时触发的事件。第二个参数是callback,在发出error事件时运行。

最后,您调用了connect()方法,该方法在默认端口6379上启动了与 Redis 的连接。

现在,您的应用已经连接到 Redis,您将修改getSpeciesData()回调,以便在初次访问时在 Redis 中存储数据,并从缓存中获取所有随后的请求的数据。

server.js文件中,添加并更新突出的代码:

 1[label fish_wiki/server.js]
 2...
 3async function getSpeciesData(req, res) {
 4  const species = req.params.species;
 5  let results;
 6  let isCached = false;
 7
 8  try {
 9    const cacheResults = await redisClient.get(species);
10    if (cacheResults) {
11      isCached = true;
12      results = JSON.parse(cacheResults);
13    } else {
14      results = await fetchApiData(species);
15      if (results.length === 0) {
16        throw "API returned an empty array";
17     }
18    }
19
20    res.send({
21      fromCache: isCached,
22      data: results,
23    });
24  } catch (error) {
25    ...
26  }
27}
28...

getSpeciesData函数中,您将isCached变量定义为false。在try块中,您将get()方法称为node-redis模块的get()方法,作为参数为species。当该方法在Redis中找到匹配species变量值的关键时,它会返回数据,然后分配给cacheResults变量。

接下來,一個「如果」聲明會檢查「cacheResults」變量是否有數據。如果符合條件,則「isCache」變量會被指定為「true」。接著,你會召喚「JSON」對象的「parse()」方法,以「cacheResults」為論點。「parse()」方法會將JSON串數據轉換為JavaScript對象。在JSON被解析後,你會召喚「send()」方法,該方法會將具有「fromCache」屬性設定為「isCached」變量的對象召喚。

如果node-redis模块的get()方法在缓存中找不到任何数据,那么cacheResults变量将被设置为null。因此,if陈述将被评估为 false。

要将数据存储在Redis缓存中,您需要使用node-redis模块的set()方法来保存它。

 1[label fish_wiki/server.js]
 2...
 3async function getSpeciesData(req, res) {
 4  const species = req.params.species;
 5  let results;
 6  let isCached = false;
 7
 8  try {
 9    const cacheResults = await redisClient.get(species);
10    if (cacheResults) {
11      isCached = true;
12      results = JSON.parse(cacheResults);
13    } else {
14      results = await fetchApiData(species);
15      if (results.length === 0) {
16        throw "API returned an empty array";
17      }
18      await redisClient.set(species, JSON.stringify(results));
19    }
20
21    res.send({
22      fromCache: isCached,
23      data: results,
24    });
25  } catch (error) {
26    ...
27  }
28}
29...

else块中,一旦数据被提取,你会调用node-redis模块的set()方法来将数据存储在species变量中的值的关键名下。

set()方法使用两个参数,这些参数是关键值对: speciesJSON.stringify(results)

第一个参数物种是数据将在 Redis 中存储的密钥,请记住物种被设置为您定义的端点传递的值,例如,当您访问/fish/red-snapper时,物种被设置为red-snapper,这将在 Redis 中成为密钥。

第二个参数是JSON.stringify(results)的值,第二个参数是JSON’sstringify()方法,参数是results变量,其中包含了从API返回的数据。该方法将JSON转换为字符串;这就是为什么,当您使用node-redis模块的get()方法提前从缓存中获取数据时,您提到了JSON.parse方法,参数是cacheResults

您的完整文件现在将看起来如下:

 1[label fish_wiki/server.js]
 2const express = require("express");
 3const axios = require("axios");
 4const redis = require("redis");
 5
 6const app = express();
 7const port = process.env.PORT || 3000;
 8
 9let redisClient;
10
11(async () => {
12  redisClient = redis.createClient();
13
14  redisClient.on("error", (error) => console.error(`Error : ${error}`));
15
16  await redisClient.connect();
17})();
18
19async function fetchApiData(species) {
20  const apiResponse = await axios.get(
21    `https://www.fishwatch.gov/api/species/${species}`
22  );
23  console.log("Request sent to the API");
24  return apiResponse.data;
25}
26
27async function getSpeciesData(req, res) {
28  const species = req.params.species;
29  let results;
30  let isCached = false;
31
32  try {
33    const cacheResults = await redisClient.get(species);
34    if (cacheResults) {
35      isCached = true;
36      results = JSON.parse(cacheResults);
37    } else {
38      results = await fetchApiData(species);
39      if (results.length === 0) {
40        throw "API returned an empty array";
41      }
42      await redisClient.set(species, JSON.stringify(results));
43    }
44
45    res.send({
46      fromCache: isCached,
47      data: results,
48    });
49  } catch (error) {
50    console.error(error);
51    res.status(404).send("Data unavailable");
52  }
53}
54
55app.get("/fish/:species", getSpeciesData);
56
57app.listen(port, () => {
58  console.log(`App listening on port ${port}`);
59});

保存和退出您的文件,并使用节点命令运行server.js:

1node server.js

一旦服务器启动,请在浏览器中更新http://localhost:3000/fish/red-snapper

注意fromCache仍然设置为false:

Page loaded without cache, displaying <code>false</code>

现在重新刷新页面,以便看到这次fromCache设置为true:

Page loaded from cache, displaying <code>true</code>

刷新页面五次,然后返回终端. 您的输出将看起来如下:

1[secondary_label Output]
2App listening on port 3000
3Request sent to the API

现在,向 API 发送的请求仅在多次更新 URL 后一次登录,这与每一次更新登录消息的最后一节相矛盾。

要进一步确认数据存储在 Redis 中,请使用CTRL+C来停止您的服务器。

1redis-cli

red-snapper键下获取数据:

1get red-snapper

您的输出将如下(编辑为简要):

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

输出显示了 API 在访问 /fish/red-snapper 终端时返回的 JSON 数据的串行版本,这确认了 API 数据存储在 Redis 缓存中。

退出 Redis 服务器客户端:

1exit

现在您可以从 API 缓存数据,您也可以设置缓存有效性。

步骤 4 – 实施缓存有效性

在缓存数据时,您需要知道数据的变化频率。一些API数据在几分钟内发生变化;其他在几个小时、几周、几个月或几年内发生变化。

在此步骤中,您将为需要在 Redis 中存储的 API 数据设置缓存有效性. 当缓存到期时,您的应用程序将向 API 发送请求以获取最近的数据。

您需要查阅您的 API 文档以设置缓存的正确期限。大多数文档都会提到数据的更新频率。然而,有些情况下文档不提供信息,因此您可能需要猜测。

一旦你选择了缓存长度,你需要将其转换为秒。为了在本教程中进行演示,你会将缓存长度设置为3分钟或180秒。

要实施缓存有效期,请打开 server.js 文件:

1nano server.js

添加突出的代码:

 1[label fish_wiki/server.js]
 2const express = require("express");
 3const axios = require("axios");
 4const redis = require("redis");
 5
 6const app = express();
 7const port = process.env.PORT || 3000;
 8
 9let redisClient;
10
11(async () => {
12  ...
13})();
14
15async function fetchApiData(species) {
16  ...
17}
18
19async function getSpeciesData(req, res) {
20  const species = req.params.species;
21  let results;
22  let isCached = false;
23
24  try {
25    const cacheResults = await redisClient.get(species);
26    if (cacheResults) {
27      isCached = true;
28      results = JSON.parse(cacheResults);
29    } else {
30      results = await fetchApiData(species);
31      if (results.length === 0) {
32        throw "API returned an empty array";
33      }
34      await redisClient.set(species, JSON.stringify(results), {
35        EX: 180,
36        NX: true,
37      });
38    }
39
40    res.send({
41      fromCache: isCached,
42      data: results,
43    });
44  } catch (error) {
45    console.error(error);
46    res.status(404).send("Data unavailable");
47  }
48}
49
50app.get("/fish/:species", getSpeciesData);
51
52app.listen(port, () => {
53  console.log(`App listening on port ${port}`);
54});

node-redis模块的set()方法中,您通过具有以下属性的对象的第三个参数:

  • EX:接受在秒内缓存时间的值
  • NX:当设置为 true时,它确保 set() 方法只能设置在 Redis 中尚不存在的密钥

保存和退出您的文件。

回到 Redis 服务器客户端来测试缓存的有效性:

1redis-cli

在 Redis 中删除red-snapper键:

1del red-snapper

退出 Redis 客户端:

1exit

现在,用node命令启动开发服务器:

1node server.js

返回浏览器并更新 http://localhost:3000/fish/red-snapper` URL. 在接下来的三分钟内,如果您更新URL,终端的输出应与以下输出一致:

1[secondary_label Output]
2App listening on port 3000
3Request sent to the API

过了三分钟后,请在浏览器中更新URL. 在终端中,您应该看到向API发送请求已经登录了两次。

1[secondary_label Output]
2App listening on port 3000
3Request sent to the API
4Request sent to the API

此输出显示缓存已过期,并再次向 API 提出请求。

您可以停用 Express 服务器。

现在您可以设置缓存有效性,您将使用中间件下一步缓存数据。

步骤 5 – 在中间件中缓存数据

在此步骤中,您将使用 Express middleware 来缓存数据. Middleware 是一个可以访问请求对象、响应对象和应对对象的函数,该函数应该在执行后运行。

要在应用程序中使用中间件来缓存,您将修改getSpeciesData()处理函数以从API中获取数据并将其存储在Redis中。

当您访问/fish/:species终端时,中间件函数将首先运行以搜索缓存中的数据;如果找到,它将返回响应,而getSpeciesData函数不会运行。

首先,打开您的server.js:

1nano server.js

接下来,删除突出的代码:

 1[label fish_wiki/server.js]
 2...
 3async function getSpeciesData(req, res) {
 4  const species = req.params.species;
 5  let results;
 6  let isCached = false;
 7
 8  try {
 9    const cacheResults = await redisClient.get(species);
10    if (cacheResults) {
11      isCached = true;
12      results = JSON.parse(cacheResults);
13    } else {
14      results = await fetchApiData(species);
15      if (results.length === 0) {
16        throw "API returned an empty array";
17      }
18      await redisClient.set(species, JSON.stringify(results), {
19        EX: 180,
20        NX: true,
21      });
22    }
23
24    res.send({
25      fromCache: isCached,
26      data: results,
27    });
28  } catch (error) {
29    console.error(error);
30    res.status(404).send("Data unavailable");
31  }
32}
33...

getSpeciesData()函数中,您删除所有搜索在Redis中存储的数据的代码,您还删除isCached变量,因为函数getSpeciesData()只会从API中获取数据并将其存储在Redis中。

一旦代码被删除,将fromCache设置为false,如下所示,因此getSpeciesData()函数将如下:

 1[label fish_wiki/server.js]
 2...
 3async function getSpeciesData(req, res) {
 4  const species = req.params.species;
 5  let results;
 6
 7  try {
 8    results = await fetchApiData(species);
 9    if (results.length === 0) {
10      throw "API returned an empty array";
11    }
12    await redisClient.set(species, JSON.stringify(results), {
13      EX: 180,
14      NX: true,
15    });
16
17    res.send({
18      fromCache: false,
19      data: results,
20    });
21  } catch (error) {
22    console.error(error);
23    res.status(404).send("Data unavailable");
24  }
25}
26...

getSpeciesData()函数从API中获取数据,将其存储在缓存中,并返回用户的响应。

接下来,添加以下代码来定义 Redis 中的缓存数据中间件函数:

 1[label fish_wiki/server.js]
 2...
 3async function cacheData(req, res, next) {
 4  const species = req.params.species;
 5  let results;
 6  try {
 7    const cacheResults = await redisClient.get(species);
 8    if (cacheResults) {
 9      results = JSON.parse(cacheResults);
10      res.send({
11        fromCache: true,
12        data: results,
13      });
14    } else {
15      next();
16    }
17  } catch (error) {
18    console.error(error);
19    res.status(404);
20  }
21}
22
23async function getSpeciesData(req, res) {
24...
25}
26...

cacheData()中间件函数采用三个参数:req,resnext。在try块中,该函数检查species变量中的值是否有数据存储在 Redis 中,如果数据处于 Redis,则返回并设置为cacheResults变量。

接下来,如果声明会检查cacheResults是否有数据,如果results变量被评估为 true,则将数据保存到results变量中,然后中间软件会使用send()方法返回一个对象,其属性fromCache设置为truedata设置为results变量。

但是,如果如果语句被评估为假,执行会切换到else块,在else块中,你会调用next(),然后将控制传递给下一个应该执行的函数。

若要將「cacheData()」中間軟件的控制轉移到「getSpeciesData()」函數時,當「next()」被召喚時,請相應地更新「express」模組的「get()」方法:

1[label fish_wiki/server.js]
2...
3app.get("/fish/:species", cacheData, getSpeciesData);
4...

现在get()方法将cacheData作为其第二个参数,这是中间软件,在Redis中搜索缓存数据并在找到时返回响应。

现在,当您访问 /fish/:species' 终端时, cacheData()' 首先执行。如果数据被缓存,它会返回响应,请求响应周期在这里结束.但是,如果在缓存中没有数据,则将召唤 getSpeciesData() 来从 API 获取数据,将其存储在缓存中,然后返回响应。

现在完整的文件将看起来像这样:

 1[label fish_wiki/server.js]
 2
 3const express = require("express");
 4const axios = require("axios");
 5const redis = require("redis");
 6
 7const app = express();
 8const port = process.env.PORT || 3000;
 9
10let redisClient;
11
12(async () => {
13  redisClient = redis.createClient();
14
15  redisClient.on("error", (error) => console.error(`Error : ${error}`));
16
17  await redisClient.connect();
18})();
19
20async function fetchApiData(species) {
21  const apiResponse = await axios.get(
22    `https://www.fishwatch.gov/api/species/${species}`
23  );
24  console.log("Request sent to the API");
25  return apiResponse.data;
26}
27
28async function cacheData(req, res, next) {
29  const species = req.params.species;
30  let results;
31  try {
32    const cacheResults = await redisClient.get(species);
33    if (cacheResults) {
34      results = JSON.parse(cacheResults);
35      res.send({
36        fromCache: true,
37        data: results,
38      });
39    } else {
40      next();
41    }
42  } catch (error) {
43    console.error(error);
44    res.status(404);
45  }
46}
47async function getSpeciesData(req, res) {
48  const species = req.params.species;
49  let results;
50
51  try {
52    results = await fetchApiData(species);
53    if (results.length === 0) {
54      throw "API returned an empty array";
55    }
56    await redisClient.set(species, JSON.stringify(results), {
57      EX: 180,
58      NX: true,
59    });
60
61    res.send({
62      fromCache: false,
63      data: results,
64    });
65  } catch (error) {
66    console.error(error);
67    res.status(404).send("Data unavailable");
68  }
69}
70
71app.get("/fish/:species", cacheData, getSpeciesData);
72
73app.listen(port, () => {
74  console.log(`App listening on port ${port}`);
75});

保存和退出您的文件。

要正确地测试缓存,您可以在 Redis 中删除红快递键,然后进入 Redis 客户端:

1redis-cli

删除Red-snapper键:

1del red-snapper

退出 Redis 客户端:

1exit

现在,运行server.js文件:

1node server.js

一旦服务器启动,返回浏览器并再次访问http://localhost:3000/fish/red-snapper

终端会记录向 API 发送请求的消息.中间软件 cacheData() 将为接下来的三分钟服务所有请求. 如果您在四分钟的时间段中随机更新 URL,您的输出将看起来类似:

1[secondary_label Output]
2App listening on port 3000
3Request sent to the API
4Request sent to the API

该行为与应用程序在上一节中如何运作一致。

您现在可以使用 middleware 在 Redis 中缓存数据。

结论

在本文中,您构建了一种从 API 获取数据并返回数据的应用程序,然后将应用程序修改为在初次访问时在 Redis 中缓存 API 响应,并为所有后续请求提供缓存数据。

作为下一步,您可以探索 Node Redis的文档,了解更多关于在node-redis模块中可用的功能. 您也可以阅读 AxiosExpress的文档,以深入了解本教程中涵盖的主题。

要继续建立 Node.js 技能,请参阅 如何在 Node.js 系列中编码

Published At
Categories with 技术
comments powered by Disqus