介绍
通过API获取数据时,会向服务器发出网络请求,收到响应数据。但是,此过程可能非常耗时,并且可能会导致程序响应时间变慢。
我们使用缓存来解决这个问题,客户端程序首先向API发送请求,将返回的数据存储在缓存中,之后的请求都从缓存中查找数据,而不是向API发送请求。
在本文中,使用 Redis做为缓存, 在 Node.js 的程序中通过4步实现数据缓存的机制。
第 1 步 --- 设置项目
在此步骤中,我们将安装该项目所需的依赖项并启动 Express 服务器。
首先,为项目创建目录mkdir
:
arduino
mkdir app
进入目录:
bash
cd app
初始化项目:
csharp
npm init -y
它将创建package.json
包含以下内容的文件:
json
{
"name": "app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": ""
},
"keywords": [],
"author": "",
"license":"ISC"
}
接下来,使用以下命令安装express
,redis
,axios
库:
npm install express redis axios
完成后,app目录结构如下:
css
app
--node_modules
--package-lock.json
--package.json
创建index.js
文件,并输入以下代码:
ini
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
保存并运行以下命令:
node index.js
输出如下:
步骤 2 --- 通过API获取数据
在刚才的index.js
中增加从API获取数据的部分:
javascript
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
async function fetchDataFromApi(userId) {
let apiUrl = 'https://xxxx/api';
if (characterId) {
apiUrl = `${apiUrl}/user/${userId}`;
} else {
apiUrl = `${apiUrl}/user`;
}
const apiResponse = await axios.get(apiUrl);
console.log("发送API请求");
return apiResponse.data;
}
app.get("/user/:id", async (req, res) => {
try {
const character = await fetchDataFromApi(req.params.id);
if (!character.length) {
throw new Error("请求异常");
}
return res.status(200).send({
fromCache: false,
data: character,
});
} catch (error) {
console.log(error);
res.status(404).send("请求异常");
}
});
app.get("/user", async (req, res) => {
try {
const characters = await fetchDataFromApi();
if (!characters.length) {
throw new Error("请求异常");
}
return res.status(200).send({
fromCache: false,
data: characters,
});
} catch (error) {
console.log(error);
res.status(404).send("请求异常");
}
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
运行 node index.js
启动服务器,之后打开 Postman 发送请求/user
, 请求耗时 885 毫秒。为了加快请求速度,我们增加 Redis 缓存。
第 3 步 --- 增加 Redis 缓存
增加缓存,只有初始访问才会从API请求数据,所有后续请求都从缓存中获取数据。
在项目中导入redis
模块:
ini
const redis = require ( "redis" );
添加以下代码连接到Redis:
javascript
let redisClient;
(async () => {
redisClient = redis.createClient();
redisClient.on("error", (error) => console.error(`Error : ${error}`));
await redisClient.connect();
})();
使用redisClient.get()
从缓存中获取数据。如果数据存在,我们将isCached
设置为 true 并分配cachedResult
给result
,如果cachedResult
为空,则我们调用API获取数据,然后使用redisClient.set()
设置到缓存中。
ini
const redisKey = "user";
let results;
let isCached = false;
const cachedResult = await redisClient.get(redisKey);
if (cachedResult) {
isCached = true;
results = JSON.parse(cachedResult);
} else {
results = await fetchDataFromApi();
if (!results.length) {
throw new Error("请求异常");
}
await redisClient.set(redisKey, JSON.stringify(results));
}
完整的文件如下:
ini
const express = require("express");
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 fetchDataFromApi(userId) {
let apiUrl = 'https://xxxx/api';
if (characterId) {
apiUrl = `${apiUrl}/user/${userId}`;
} else {
apiUrl = `${apiUrl}/user`;
}
const apiResponse = await axios.get(apiUrl);
console.log("发送API请求");
return apiResponse.data;
}
app.get("/user/:id", async (req, res) => {
try {
const redisKey = `user-${req.params.id}`;
let results;
let isCached = false;
const cachedResult = await redisClient.get(redisKey);
if (cachedResult) {
isCached = true;
results = JSON.parse(cachedResult);
} else {
results = await fetchDataFromApi(req.params.id);
if (!results.length) {
throw new Error("请求异常");
}
await redisClient.set(redisKey, JSON.stringify(results));
}
return res.status(200).send({
fromCache: isCached,
data: results,
});
} catch (error) {
console.log(error);
res.status(404).send("请求异常");
}
});
app.get("/user", async (req, res) => {
try {
const redisKey = "user";
let results;
let isCached = false;
const cachedResult = await redisClient.get(redisKey);
if (cachedResult) {
isCached = true;
results = JSON.parse(cachedResult);
} else {
results = await fetchDataFromApi();
if (!results.length) {
throw new Error(请求异常");
}
await redisClient.set(redisKey, JSON.stringify(results));
}
return res.status(200).send({
fromCache: isCached,
data: results,
});
} catch (error) {
console.log(error);
res.status(404).send("请求异常");
}
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
保存并运行以下命令:
node index.js
服务启动后,我们通过Postman发送请求,发现第一次请求比较慢,后边的请求速度都变快了,从868毫秒缩短到14毫秒。
到目前为止,我们可以从 API 获取数据,并把数据设置到到缓存中,后续请求从缓存中取数据。 为了防止API端数据变化,但是缓存数据没变的情况,还需要设置缓存有效性。
第 4 步 --- 设置缓存有效性
当缓存数据时,要设置合适的过期时间,具体时间可以根据业务情况决定。 在本文中,将缓存持续时间设置为120秒。
在index.js
中增加以下内容:
csharp
await redisClient.set(redisKey, JSON.stringify(results), {
EX: 120,
NX: true,
});
EX
:缓存有效时间(单位:秒)。NX
:当设置为时true
,set()
方法只设置 Redis 中不存在的键。
把以上代码添加到API接口中:
javascript
app.get("/user/:id", async (req, res) => {
try {
const redisKey = `user-${req.params.id}`;
results = await fetchDataFromApi(req.params.id);
if (!results.length) {
throw new Error("请求异常");
}
await redisClient.set(redisKey, JSON.stringify(results), {
EX: 120,
NX: true,
});
return res.status(200).send({
fromCache: isCached,
data: results,
});
} catch (error) {
console.log(error);
res.status(404).send("请求异常");
}
});
app.get("/user", async (req, res) => {
try {
const redisKey = "user";
results = await fetchDataFromApi();
if (!results.length) {
throw new Error("请求异常");
}
await redisClient.set(redisKey, JSON.stringify(results), {
EX: 120,
NX: true,
});
return res.status(200).send({
fromCache: isCached,
data: results,
});
} catch (error) {
console.log(error);
res.status(404).send("请求异常");
}
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
现在创建一个中间件,从缓存中获取数据:
ini
async function cacheData(req, res, next) {
try {
const characterId = req.params.id;
let redisKey = "user";
if (characterId) {
redisKey = `user-${req.params.id}`;
}
const cacheResults = await redisClient.get(redisKey);
if (cacheResults) {
res.send({
fromCache: true,
data: JSON.parse(cacheResults),
});
} else {
next();
}
} catch (error) {
console.error(error);
res.status(404);
}
}
把cacheData
加入到API调用中
vbnet
app.get("/user/:id", cacheData, async (req, res) => {
try {
const redisKey = `hogwarts-character-${req.params.id}`;
results = await fetchDataFromApi(req.params.id);
if (!results.length) {
throw new Error("请求异常");
}
await redisClient.set(redisKey, JSON.stringify(results), {
EX: 120,
NX: true,
});
return res.status(200).send({
fromCache: false,
data: results,
});
} catch (error) {
console.log(error);
res.status(404).send("请求异常");
}
});
app.get("/user", cacheData, async (req, res) => {
try {
const redisKey = "user";
results = await fetchDataFromApi();
if (!results.length) {
throw new Error("请求异常");
}
await redisClient.set(redisKey, JSON.stringify(results), {
EX: 120,
NX: true,
});
return res.status(200).send({
fromCache: false,
data: results,
});
} catch (error) {
console.log(error);
res.status(404).send("请求异常");
}
});
当访问API时/user
,cacheData()
首先执行。如果数据被缓存,它将返回响应。如果在缓存中没有找到数据,则会从API请求数据,将其存储在缓存中,并返回响应。
再次利用Postman来验证接口,发现响应时间只有15毫秒左右。
总结
在本文中,我们构建了一个 Node.js 程序,该程序 API 获取数据并使用Redis
进行缓存,只有初次请求和缓存过期后才从API获取数据,之后的请求都从缓存中获取数据,大大缩短请求所用的时间。