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

接下来,使用以下命令安装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获取数据,之后的请求都从缓存中获取数据,大大缩短请求所用的时间。

相关推荐
逍遥5966070103 小时前
网络设备交换机路由器 AI 配置助手
node.js·硬件工程
damoluomu6 小时前
Directus 换掉 GPL 之后,Node.js CMS 到底怎么选?(三)
node.js
DevUp6 小时前
Directus 换掉 GPL 之后,Node.js CMS 到底怎么选?(三)
开源·node.js·cms
范中勤7 小时前
LLM 动态加载与多用户缓存架构技术文档
redis·langchain·llm·缓存架构·多用户隔离
小小龙学IT8 小时前
Redis 源码深度解析:从常用命令到内部实现
redis·golang·开源
明月_清风19 小时前
Deno 终局来了:从挑战 Node 到被 Cloudflare 收编
前端·后端·node.js
禾小西20 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis
骉马代驾1 天前
代驾系统实战(三):抢单池的 Redis 锁 + RabbitMQ 异步消费怎么做
redis
guslegend1 天前
脚手架原理与本地调试:从 bin 软链接到 npm link
前端·npm·node.js·脚手架·前端工程化
行百里er1 天前
Redis Streams——有确认、能回溯的消息队列
redis