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

news2025/2/27 4:28:28

介绍

通过API获取数据时,会向服务器发出网络请求,收到响应数据。但是,此过程可能非常耗时,并且可能会导致程序响应时间变慢。

我们使用缓存来解决这个问题,客户端程序首先向API发送请求,将返回的数据存储在缓存中,之后的请求都从缓存中查找数据,而不是向API发送请求。

在本文中,使用 Redis做为缓存, 在 Node.js 的程序中通过4步实现数据缓存的机制。

第 1 步 — 设置项目

在此步骤中,我们将安装该项目所需的依赖项并启动 Express 服务器。

首先,为项目创建目录mkdir

mkdir app

进入目录:

cd app

初始化项目:

npm init -y

它将创建package.json包含以下内容的文件:

{

    "name": "app",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": ""
    },
    "keywords": [],
    "author": "",
    "license":"ISC"
}

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

npm install express redis axios

完成后,app目录结构如下:

app
--node_modules
--package-lock.json
--package.json

创建index.js文件,并输入以下代码:

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

输出如下:

image.png

步骤 2 — 通过API获取数据

在刚才的index.js中增加从API获取数据的部分:

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模块:

const redis = require ( "redis" );

添加以下代码连接到Redis:

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()设置到缓存中。

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));  
}

完整的文件如下:

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中增加以下内容:

await redisClient.set(redisKey, JSON.stringify(results), {  
    EX: 120,  
    NX: true,  
});
  • EX:缓存有效时间(单位:秒)。
  • NX:当设置为时trueset()方法只设置 Redis 中不存在的键。

把以上代码添加到API接口中:

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}`);
});

现在创建一个中间件,从缓存中获取数据:

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调用中

app.get("/user/:id", cacheData, 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: 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获取数据,之后的请求都从缓存中获取数据,大大缩短请求所用的时间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1027247.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库:Hive转Presto(一)

本人因为工作原因,经常使用hive以及presto,一般是编写hive完成工作,服务器原因,presto会跑的更快一些,所以工作的时候会使用presto验证结果,所以就要频繁hive转presto,为了方便,我用…

Shader实战(2):在unity中实现物体材质随时间插值渐变

目录 前言 一、shader代码 二、材质准备 三、控制代码 前言 最近想做一个物体两套材质随时间插值渐变的效果,本以为可以通过unity自带的Material.Lerp()实现,后来发现这个方法只适用于纯色的情况,其实与Color.Lerp()是同样的效果&#xf…

深度分析Oracle中的NULL

【squids.cn】 全网zui低价RDS,免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 关键点 特殊值NULL意味着没有数据,它声明了该值是未知的事实。默认情况下,任何类型的列和变量都可以取这个值,除非它们有一个NOT N…

阿里云产品试用系列-云桌面电脑

无影云电脑(WUYING Workspace),是一种易用、安全、高效的云上桌面服务。它支持快速便捷的桌面环境创建、部署、统一管控与运维。无需前期传统硬件投资,帮您快速构建安全、高性能、低成本的企业桌面办公体系。可广泛应用于具有高数…

[Linux入门]---文本编辑器vim使用

文章目录 1.Linux编辑器-vim使用2.vim的基本概念4.vim正常模式命令集从正常模式进入插入模式从插入模式转换为命令模式移动光标删除文字复制替换撤销更改跳至指定行 5.vim末行模式命令集5.总结 1.Linux编辑器-vim使用 vi/vim作为Linux开发工具之一,从它的键盘操作图…

(VS报错)已在 xxxxx.exe 中执行断点指令(__debugbreak()语句或类似调用)-解决方法C++创建对象四种方式

上述报错困扰了我好几天,在网上搜了一天,到最后还是没有解决问题 试过通过项目属性->C/C>代码生成->启用增强指令集->选择AVX,这种方法也没用 但问题出现在创建对象时内存分配问题上 方法一: 如果是这样创建对象&a…

Linux学习-HIS系统(1)

Git安装 #安装中文支持(选做) [rootProgramer ~]# echo $LANG #查看当前系统语言及编码 en_US.UTF-8 [rootProgramer ~]# yum -y install langpacks-zh_CN.noarch #安装中文支持 [rootProgramer ~]# vim /etc/locale.co…

将docker镜像打成tar包

# 打包 docker save -o zookeeper.tar bitnami/zookeeper:3.9.0-debian-11-r11# 解压 docker load -i zookeeper.tar

Jenkins学习笔记2

Jenkins下载安装: 从清华源开源镜像站上下载jenkins的安装包: 安装的是这个版本。 关于软件的版本,尽量使用LTS,长期支持。 首先是安装openjdk: yum install fontconfig java-11-openjdk[rootlocalhost soft]# java …

springcloudalibaba和nacos版本对应关系

文章目录 一、背景二、解决bug历程 一、背景 因为公司项目需要升级springcloud的版本,升级后服务启动时连接不上nacos(如下图) 二、解决bug历程 历程一 一开始直接百度“Client not connected, current status:STARTING”这个错误&#x…

【基于Thread多线程+随机数(Random)+java版本JDBC手动提交事务+EasyExcel读取excel文件,向数据库生成百万级别模拟数据】

基于Thread多线程随机数(Random)java版本JDBC手动提交事务EasyExcel读取excel文件,向数据库生成百万级别模拟数据 基于Thread多线程随机数(Random)java版本JDBC手动提交事务EasyExcel读取excel文件,向数据库…

最佳实践:TiDB 业务写变慢分析处理

作者:李文杰 数据架构师,TUG 广州地区活动组织者 在日常业务使用或运维管理 TiDB 的过程中,每个开发人员或数据库管理员都或多或少遇到过 SQL 变慢的问题。这类问题大部分情况下都具有一定的规律可循,通过经验的积累可以快速的定…

9.20号作业实现钟表

1.widget.h #include <QPainter> //画家 #include <QTimerEvent> #include <QTime> #include<QTimer> //定时器类QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Wid…

基于Xml方式Bean的配置-初始化方法和销毁方法

SpringBean的配置详解 Bean的初始化和销毁方法配置 Bean在被实例化后&#xff0c;可以执行指定的初始化方法完成一些初始化的操作&#xff0c;Bean在销毁之前也可以执行指定的销毁方法完成一些操作&#xff0c;初始化方法名称和销毁方法名称通过 <bean id"userService…

Linux 目录结构介绍

对上面的说明: root 目录 &#xff1a; linux 超级权限 root 的主目录 home 目录 &#xff1a; 系统默认的用户主目录&#xff0c;如果添加用户是不指定用户的主目录&#xff0c;默认在/home 下创建与用户同名的文件夹 bin 目录 &#xff1a; 存放系统所需要的重要命令&am…

设计模式:简单工厂、工厂方法、抽象工厂

参考 Java设计模式之创建型&#xff1a;工厂模式详解&#xff08;简单工厂工厂方法抽象工厂&#xff09; - 知乎 工厂方法 以生产手机为例&#xff0c;具体的UML图如下&#xff1a; 这种方法的优点是对于用户来说&#xff0c;不再需要面对具体的生产逻辑&#xff0c;只需要将生…

k8s的安装

我这里使用vmware创建了三台虚拟机&#xff0c;k8s的虚拟机建议最少2核、4G内存&#xff0c;我的电脑配置不高采用的2核、3G的配置&#xff1b; 安装k8s之前需要先安装docker&#xff0c;docker的安装参考&#xff1a;docker的安装及使用_docker的安装和使用_骑士999111的博客-…

光伏电池建模及温度光照的影响曲线MATLAB仿真

微❤关注“电气仔推送”获得资料 模型介绍&#xff1a; 需要MATLAB2018B及以上的版本&#xff01;&#xff01; 首先根据根据环境修正公式搭建光伏电池仿真模型&#xff1a; 温度变化下的IU、PU仿真及曲线&#xff1a; 光照变化下的IU、PU仿真及曲线&#xff1a; 文件说明&a…

SSD上 NVIDIA Jetson Orin NANO系統如何刷

对于AI计算性能高达40TOPS的Jetson Orin Nano开发套件来说&#xff0c;如果缺少性能够好的存储相匹配&#xff0c;会让总体执行效益大打折扣。为此&#xff0c;NVIDIA在Jetson Orin Nano开发套件上配置2个M.2接口&#xff08;如下图&#xff09;&#xff0c;最高能安装2片高速P…

Spring Boot启动源码分析

一&#xff0c;前言 版本&#xff1a;spring-boot-starter-parent版本为2.3.0 Spring Boot项目的启动入口是一个main方法&#xff0c;因此我们从该方法入手即可 二&#xff0c;源码分析 跟踪run方法 /*** SpringApplication的方法* param primarySource 启动类的class* p…