只需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获取数据,之后的请求都从缓存中获取数据,大大缩短请求所用的时间。

相关推荐
Clownseven3 小时前
Docker+Nginx+Node.js实战教程:从零搭建高可用的前后端分离项目
nginx·docker·node.js
lssjzmn5 小时前
🚀如何基于Redis的ZSet数据结构设计一个通用的,简单的,可靠的延迟消息队列,以RedisTemplate为例
redis
jakeswang7 小时前
应用缓存不止是Redis!——亿级流量系统架构设计系列
redis·分布式·后端·缓存
PineappleCoder7 小时前
同源策略是啥?浏览器为啥拦我的跨域请求?(一)
前端·后端·node.js
.Shu.8 小时前
Redis zset 渐进式rehash 实现原理、触发条件、执行流程以及数据一致性保障机制【分步源码解析】
数据库·redis·缓存
君不见,青丝成雪8 小时前
大数据技术栈 —— Redis与Kafka
数据库·redis·kafka
悟能不能悟9 小时前
排查Redis数据倾斜引发的性能瓶颈
java·数据库·redis
切糕师学AI9 小时前
.net core web程序如何设置redis预热?
redis·.netcore
stoneSkySpace9 小时前
pnpm 和 npm 差异
前端·npm·node.js
Mi_Manchikkk9 小时前
Java高级面试实战:Spring Boot微服务与Redis缓存整合案例解析
java·spring boot·redis·缓存·微服务·面试