只需4步使用Redis缓存优化Node.js应用
介绍
通过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"
}
接下来,使用以下命令安装express
,redis
,axios
库:
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
输出如下:
步骤 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 并分配cachedResult
给result
,如果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
:当设置为时true
,set()
方法只设置 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时/user
,cacheData()
首先执行。如果数据被缓存,它将返回响应。如果在缓存中没有找到数据,则会从API请求数据,将其存储在缓存中,并返回响应。
再次利用Postman来验证接口,发现响应时间只有15毫秒左右。
总结
在本文中,我们构建了一个 Node.js 程序,该程序 API 获取数据并使用Redis
进行缓存,只有初次请求和缓存过期后才从API获取数据,之后的请求都从缓存中获取数据,大大缩短请求所用的时间。
相关文章:

只需4步使用Redis缓存优化Node.js应用
介绍 通过API获取数据时,会向服务器发出网络请求,收到响应数据。但是,此过程可能非常耗时,并且可能会导致程序响应时间变慢。 我们使用缓存来解决这个问题,客户端程序首先向API发送请求,将返回的数据存储…...
【react基础01】项目文件结构描述
react 项目文件结构描述 📂 REACTWORKSPACE📂 node_modules📂 public📄 favicon.ico📄 index.html📄 logo192.png📄 logo512.png📄 manifest.json📄 robots.txt …...

光电开关-NPN-PNP
基础概念 有信号 “检测到物体/有物体遮挡” 工作原理 NPN:表示共正电压,输出负电压【只能输出低电压或者悬空 常开常闭是指 输出有没有跟“地”接通】; NPN NO:表示常态下是常开的,检测到物体时黑色线输出一个负电压…...

学会使用Git 和 GitHub
Git 和 GitHub 都是程序员每天都要用到的东西 —— 前者是目前最先进的 版本控制工具,拥有最多的用户,且管理着地球上最庞大的代码仓库;而后者是全球最大 同性交友 代码托管平台、开源社区。 在没有这两个工具时,编程可能是这样的…...

SoftwareTest3 - 要了人命的Bug
软件测试基础篇 一 . 如何合理的创建一个 Bug二 . Bug 等级2.1 崩溃2.2 严重2.3 一般2.4 次要 三 . Bug 的生命周期四 . 跟开发产生争执应该怎么解决 Hello , 大家好 , 又给大家带来新的专栏喽 ~ 这个专栏是专门为零基础小白从 0 到 1 了解软件测试基础理论设计的 , 虽然还不足…...
Linux系统中MySQL库的操作,实操sql代码
Linux系统中MySQL库的操作 本文主要是对linux系统下MySQL库操作的总结,包含创建、删除、修改数据库,数据库的编码格式和校验格式以及数据库的恢复和备份。 1.创建数据库 1.1基本语法: CREATE DATABASE [IF NOT EXISTS] db_name [create_s…...
Python基础分享之面向对象的进一步拓展
我们熟悉了对象和类的基本概念。我们将进一步拓展,以便能实际运用对象和类。 调用类的其它信息 上一讲中提到,在定义方法时,必须有self这一参数。这个参数表示某个对象。对象拥有类的所有性质,那么我们可以通过self,调…...

Windows安装Docker Desktop并配置镜像、修改内存占用大小
启用Hyper-V Win S 搜索控制面板 安装WSL2 第一种方法(推荐) 以管理员运行命令提示符,然后重启Docker Desktop wsl --updatewsl --set-default-version 2第2种方法去微软官网下载WSL2并安装 《微软官网下载WSL2》 配置WSL2最大内…...

Zipping
Zipping 信息收集端口扫描目录扫描webbanner信息收集 漏洞利用空字节绕过---->失败sqlI-preg_match bypass反弹shell 稳定维持 提权-共享库漏洞 参考:https://rouvin.gitbook.io/ibreakstuff/writeups/htb-season-2/zipping#sudo-privileges-greater-than-stock-…...

pytorch学习---实现线性回归初体验
假设我们的基础模型就是y wx b,其中w和b均为参数,我们使用y 3x0.8来构造数据x、y,所以最后通过模型应该能够得出w和b应该分别接近3和0.8。 步骤如下: 准备数据计算预测值计算损失,把参数的梯度置为0,进行反向传播…...

别再乱写git commit了
B站|公众号:啥都会一点的研究生 写在前面 在很长的一段时间中,使用git commit都是随心所欲,log肥肠简洁,随着代码的迭代,当时有多偷懒,返过头查看git日志就有多懊悔,就和写代码不写doc string…...

八大排序(一)冒泡排序,选择排序,插入排序,希尔排序
一、冒泡排序 冒泡排序的原理是:从左到右,相邻元素进行比较。每次比较一轮,就会找到序列中最大的一个或最小的一个。这个数就会从序列的最右边冒出来。 以从小到大排序为例,第一轮比较后,所有数中最大的那个数就会浮…...
泊松分布简要介绍
泊松分布是一种常见的离散概率分布,它用于描述某个时间段或区域内随机事件发生的次数。它得名于法国数学家西蒙丹尼泊松。 泊松分布的概率质量函数表示某个时间段或区域内事件发生次数的概率。如果随机变量 X 服从泊松分布,记作 X ~ Poisson(λ)&#x…...

C语言每日一题(10):无人生还
文章主题:无人生还🔥所属专栏:C语言每日一题📗作者简介:每天不定时更新C语言的小白一枚,记录分享自己每天的所思所想😄🎶个人主页:[₽]的个人主页🏄…...
VSCode开发go手记
断点调试: 安装delve(windows): go get -u github.com/go-delve/delve/cmd/dlv 设置 launch.json 配置文件: ctrlshiftp 输入 Debug: Open launch.json 打开 launch.json 文件,如果第一次打开,会新建一…...

怎么选择AI伪原创工具-AI伪原创工具有哪些
在数字时代,创作和发布内容已经成为了一种不可或缺的活动。不论您是个人博主、企业家还是网站管理员,都会面临一个共同的挑战:如何在互联网上脱颖而出,吸引更多的读者和访客。而正是在这个背景下,AI伪原创工具逐渐崭露…...

【块状链表C++】文本编辑器(指针中 引用 的使用)
》》》算法竞赛 /*** file * author jUicE_g2R(qq:3406291309)————彬(bin-必应)* 一个某双流一大学通信与信息专业大二在读 * * brief 一直在竞赛算法学习的路上* * copyright 2023.9* COPYRIGHT 原创技术笔记:转载…...

echarts的Y轴设置为整数
场景:使用echarts,设置Y轴为整数。通过判断Y轴的数值为整数才显示即可 yAxis: [{name: ,type: value,min: 0, // 最小值// max: 200, // 最大值// splitNumber: 5, // 坐标轴的分割段数// interval: 100 / 5, // 强制设置坐标轴分割间隔度(取本Y轴的最大…...

恢复删除文件?不得不掌握的4个方法!
“删除了的文件还可以恢复吗?有个文件我本来以为不重要了,就把它删除了,没想到现在还需要用到!这可怎么办?有没有办法找回来呢?” 重要的文件一旦丢失或误删可能都会对我们的工作和学习造成比较大的影响。怎…...
GitLab CI/CD:.gitlab-ci.yml 文件常用参数小结
文章目录 一、.gitlab-ci.yml 文件作用二、一个简单的.gitlab-ci.yml 文件示例参考 一、.gitlab-ci.yml 文件作用 可以定义跑CI时想要运行的命令或脚本 可以定义job之间的依赖和缓存 可以执行程序部署并定义部署位置 可以定义想要包含的其他配置文件和模版 二、一个简单的.gi…...
动手学深度学习12.7. 参数服务器-笔记练习(PyTorch)
以下内容为结合李沐老师的课程和教材补充的学习笔记,以及对课后练习的一些思考,自留回顾,也供同学之人交流参考。 本节课程地址:35 分布式训练【动手学深度学习v2】_哔哩哔哩_bilibili 本节教材地址:12.7. 参数服务器…...
Kerberos面试内容整理-在 Linux/Windows 中的 Kerberos 实践
Windows 实践: 在Windows环境中,Kerberos 几乎是无形融合的。用户使用域账号登录计算机时,实际上就完成了Kerberos的AS认证并获取TGT;此后的资源访问(如共享文件夹、打印机、数据库等)都会自动使用Kerberos进行验证,而无需用户干预。Windows通过LSASS进程维护和缓存用户…...

spring:实例化类过程中方法执行顺序。
如题。在实例化Bean时,会根据配置依次调用方法。在此测试代码如下: 在测试类中继承接口InitializingBean,接口InterfaceUserService(该接口为自定义,只是定义set方法)。 InterfaceUserService,…...

如何在 Windows 11 中永久更改默认浏览器:阻止 Edge 占据主导地位
在 Windows 11 中更改默认浏览器对于新手或技术不太熟练的用户来说可能会令人沮丧。 为什么要在 Windows 11 中更改默认浏览器? 这是一个重要的问题:你为什么要从 Microsoft Edge 切换过来? 生态系统集成:如果你已经在广泛使用 Google 服务,Chrome 可以提供无缝集成。同…...
Python应用break初解
大家好!作为 Python 初学者,控制循环的执行是编程中的基础技能之一。在本文中,我们将深入探讨break语句的用途和用法,帮助您更好地理解和掌握这一强大的工具。 定义: break是 Python 中的一个保留关键字,用于在循环中提前终止循环…...

JAVA理论-JAVA基础知识
1.Java 基础 知识 1.1 面向对象的特征(了解) 面向对象的特征:封装、继承、多态、抽象 封装:就是把对象的属性和行为(数据)结合为一个独立的整体,并尽量隐藏对象的内部细节,公开我希…...
沪铜6月想法
市场回顾 2025年5月,沪铜期货主力合约价格整体呈现震荡走势。从月初到月末,价格在7.67-7.82万元/吨之间波动。截至5月31日,沪铜主力合约收盘价为7.76万元/吨。本月铜价围绕供需基本面和宏观政策预期展开波动,尤其在5月14日至5月1…...

Web前端之原生表格动态复杂合并行、Vue
MENU 效果公共数据纯原生StyleJavaScript vue原生table 效果 原生的JavaScript原生table null 公共数据 const list [{id: "a1",title: "第一列",list: [{id: "a11",parentId: "a1",title: "第二列",list: [{ id: "…...

GC1809:高性能音频接收与转换芯片
GC1809 是一款高性能音频接收与转换芯片,适用于多种音频设备,如 A/V 接收器、多媒体音响设备、机顶盒等。本文将简要介绍该芯片的主要特性、性能参数及应用。 主要特性 多协议兼容:兼容 IEC60958、S/PDIF、EIAJ CP1201 和 AES3 协议。 多种…...
vue:当前对象添加对应值
this.$set(this.dynamicValidateForm.domains[index], // 目标对象item.roundProperty, // 要添加的属性名row[item.roundProperty] ? row[item.roundProperty].split(",") : [] // 属性值 ); 说明: 第一个参数:th…...