只需4步使用Redis缓存优化Node.js应用

介绍

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

接下来,使用以下命令安装expressredisaxios库:

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 并分配cachedResultresult,如果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:当设置为时trueset()方法只设置 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时/usercacheData()首先执行。如果数据被缓存,它将返回响应。如果在缓存中没有找到数据,则会从API请求数据,将其存储在缓存中,并返回响应。

再次利用Postman来验证接口,发现响应时间只有15毫秒左右。

总结

在本文中,我们构建了一个 Node.js 程序,该程序 API 获取数据并使用Redis进行缓存,只有初次请求和缓存过期后才从API获取数据,之后的请求都从缓存中获取数据,大大缩短请求所用的时间。

相关推荐
程楠楠&M4 小时前
node.js第三方Express 框架
前端·javascript·node.js·express
盛夏绽放4 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
fpcc4 小时前
redis6.0之后的多线程版本的问题
c++·redis
刘九灵4 小时前
Redis ⽀持哪⼏种数据类型?适⽤场景,底层结构
redis·缓存
Roc.Chang6 小时前
macos 使用 nvm 管理 node 并自定义安装目录
macos·node.js·nvm
登云时刻7 小时前
Kubernetes集群外连接redis集群和使用redis-shake工具迁移数据(一)
redis·kubernetes·bootstrap
怕冷的火焰(~杰)8 小时前
Node基本使用
node.js
_半夏曲9 小时前
node.js、nginx、iis、tomcat针对部署方面的简述
nginx·node.js·tomcat
生椰拿铁You10 小时前
09 —— Webpack搭建开发环境
前端·webpack·node.js
煎饼小狗13 小时前
Redis五大基本类型——Zset有序集合命令详解(命令用法详解+思维导图详解)
数据库·redis·缓存