简介:
在本教程中,我们将学习缓存的基础知识以及如何使用redis和typeScript/nodejs实现它们。但是在开始之前,让我们从基础开始。
什么是缓存?
缓存是计算机系统和软件应用程序中用于快速存储和检索数据的技术。它涉及将经常访问或昂贵的计算数据的副本存储在临时存储位置,称为缓存,以便将来可以更快地提供相同数据的请求。
缓存的目的是通过减少从其原始来源获取数据所需的时间和资源来提高系统的性能和效率。与其从原始位置检索数据,该数据可能涉及磁盘访问或网络通信(网络通信)的耗时操作,而是从缓存中检索数据,该缓存通常位于靠近请求者并提供更快的访问权限。
。 先决条件
要遵循本教程,您将需要对NodeJ和打字稿有基本的了解。我会尽力解释每个必要的步骤,我相信我可以提供详尽的解释以在整个过程中为您提供帮助。
为什么REDIS?
在缓存方面,Redis由于几个原因而脱颖而出。它的功能和功能使其成为优化性能和提高整体系统效率的理想解决方案。
Redis以其令人难以置信的速度和性能而闻名。通过将数据存储在内存中,REDIS启用了闪电 - 快速的数据访问和检索,使其适用于需要实时数据处理和高吞吐量的应用程序。凭借能够处理每秒数百万请求并提供低延迟响应时间的能力,Redis在速度最重要的方案中表现出色。
通过利用Redis作为缓存,应用程序可以将经常访问的数据存储在内存中,从而消除了重复和资源密集型操作的需求。这会提高性能,减少响应时间和更无缝的用户体验。
在本教程中,我们将使用REDIS来缓存从外部API检索的数据,其响应时间明显缓慢。我们将将这些数据存储在我们的缓存中,并将其用于对API提出的后续请求。但是,当来自原始API的数据发生变化时,我们还将解决该方案。我们将实施一种机制,以确保我们的缓存始终从API提供最新数据,以确保我们的系统与最新信息保持同步。
让我们开始。
首先,让我们初始化我们的nodejs项目并安装所需的依赖项。
npm init -y
npm i express axios cors dotenv redis
现在安装类型定义:
npm i @types/express @types/axios @types/cors @types/dotenv @types/redis
拥有tsconfig.json文件确保在不同环境中的编译过程中的一致性,并允许简单的项目配置和维护。
所以继续使用:
创建一个tsc --init
然后配置您的 Outdir 和 rootdir 与以下结构匹配的选项。
不要忘记通过在专用终端中使用tsc -w
观察打字稿代码中的更改。
接下来,让我们连接到redis。为此,您有两个选择。
- 使用localhost连接到redis-local-server。
- 通过Abiaoqian创建实例,连接到redis-cloud-console
在本教程中,我们将使用选项2。(如果您还没有帐户,请创建一个并创建数据库实例)。
现在让我们连接到我们的数据库。我们的./src/config.ts
文件现在应该具有以下代码行:
import { createClient } from 'redis';
import dotenv from 'dotenv';
dotenv.config();
export const client = createClient({
password: process.env.REDIS_PASSWORD,
socket: {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379', 10)
}
});
上面的代码使用REDIS软件包设置了REDIS客户端,使用DOTENV从.env文件加载环境变量,并导出Redis客户端以用于代码库的其他部分。环境变量用于配置REDIS连接,包括主机,端口和密码。 (这些变量在您的数据库实例上可用,一旦您在此处创建帐户:Redis-cloud)
接下来,让我们在我们的app.ts
文件中导入客户端变量,然后连接到数据库。
配置您的app.ts
文件看起来像这样:
import dotenv from "dotenv"
import express, { Request, Response } from "express"
import axios from "axios"
import { client } from "./config/connect"
import cors from "cors"
dotenv.config()
const app : express.Application = express()
const PORT = process.env.PORT || 7000
app.use(cors())
app.use(express.json())
const start = async () => {
try {
await client.connect()
app.listen(PORT, () => {
console.log(`Server is connected to redis and is listening on port ${PORT}`)
})
} catch (error) {
console.log(error)
}
}
start()
上面的代码导入我们为此项目需要的所有已安装依赖项,设置Express服务器,配置其处理JSON请求,启用CORS,连接到REDIS客户端,并启动服务器以侦听传入请求,如果连接成功。
现在,对于本教程,我创建了一个API并部署了一个API,其响应被5秒延迟,以证明在现实世界中的缓存如何有用。
因此,在我们的app.ts
文件中,我们将创建两个函数,以这样的API调用:
async function isDataModified () {
const response = await axios.get("https://pleasant-newt-girdle.cyclic.app/api/modified")
return response.data.modified
}
async function getAllUsers () {
const response = await axios.get("https://pleasant-newt-girdle.cyclic.app/api/users")
return response.data
}
现在,让我们分别检查这些功能。第一个功能将请求发送到与其数据库交互的端点。它检索了boolean
结果,如果有任何最近的更改,该结果将评估为真。这些更改包括场景,例如添加了新项目POST
,对现有项目PUT
的修改或项目DELETE
的删除。在没有任何更改的情况下,结果将是错误的。
另一方面,第二个功能直接检索了该特定API数据库中所有项目的列表(在这种情况下,用户)。
现在,让我们了解这种方法背后的理由。我们为什么要向API提出两个请求?还记得我们提到只需缓存最新信息时吗?确切地!那么,当用户决定更新其个人资料的特定详细信息时会发生什么?在这种情况下,我们还需要更新缓存,对吗?完全正确。这正是第一个函数所完成的。在服务我们的响应之前,我们会验证API数据库中是否进行了任何更改,以确保我们的缓存是最新的。
现在,让我们在此API中创建一个端点,以存储和从缓存中检索信息。使用此端点更新app.ts
文件。
app.use("/get-users", async (req : Request, res : Response) => {
let result;
let isCahed;
try {
const data = await isDataModified()
if (data === true) {
result = await getAllUsers()
isCahed = false
await client.set("all_users", JSON.stringify(result))
}
else {
const isCahedInRedis = await client.get("all_users");
if (isCahedInRedis) {
isCahed = true
result = JSON.parse(isCahedInRedis)
}
else {
result = await getAllUsers()
isCahed = false
await client.set("all_users", JSON.stringify(result))
}
}
return res.status(200).json({
isCahed,
result : result
})
} catch (error) {
console.log(error)
return res.status(500).json({error})
}
})
现在,让我们解释一下这里发生的事情:
路由处理程序功能:
a。创建了两个变量result
和isCached
,以存储API请求结果和缓存状态。
b。调用isDataModified()
功能检查数据库中是否进行了任何修改。结果存储在data
变量中。
c。如果检测到修改(当数据为真时),则意味着需要更新缓存。 getAllUsers()
函数被调用以从API中检索所有用户数据。结果被分配给result
变量,并且isCached
变量设置为false
。然后,使用client.set()
方法将检索到的数据存储在REDIS缓存中。
d。如果未检测到修改,则意味着缓存数据仍然有效。代码使用client.get()
方法检查数据是否已在REDIS中缓存。如果存在缓存的数据,则将其分配给result
变量,并且isCached
变量设置为true。
e。如果不存在缓存的数据,则调用getAllUsers()
功能从API检索用户数据。结果被分配给result
变量,并且isCached
变量设置为false
。然后,使用client.set()
方法将检索到的数据存储在REDIS缓存中。
f。最后,该代码发送了一个状态为200的JSON响应。响应包括ISCACHED状态和结果数据。
如果在此过程中发生任何错误,则将它们捕获在捕获块中。返回500状态并返回错误消息的JSON响应。
总而言之,此代码设置了一个端点,从API获取用户数据。它检查数据是否已修改,在需要时更新缓存,并返回缓存的数据或从API中检索新的数据。
这几乎可以完成工作。我们的app.ts
文件现在应该看起来像这样:
import dotenv from "dotenv"
import express, { Request, Response } from "express"
import axios from "axios"
import { client } from "./config/connect"
import cors from "cors"
dotenv.config()
const app : express.Application = express()
const PORT = process.env.PORT || 7000
app.use(cors())
app.use(express.json())
async function isDataModified () {
const response = await axios.get("https://pleasant-newt-girdle.cyclic.app/api/modified")
return response.data.modified
}
async function getAllUsers () {
const response = await axios.get("https://pleasant-newt-girdle.cyclic.app/api/users")
return response.data
}
app.use("/get-users", async (req : Request, res : Response) => {
let result;
let isCached;
try {
const data = await isDataModified()
if (data === true) {
result = await getAllUsers()
isCached = false
await client.set("all_users", JSON.stringify(result))
}
else {
const isCachedInRedis = await client.get("all_users");
if (isCachedInRedis) {
isCached = true
result = JSON.parse(isCachedInRedis)
}
else {
result = await getAllUsers()
isCached = false
await client.set("all_users", JSON.stringify(result))
}
}
return res.status(200).json({
isCached,
result : result
})
} catch (error) {
console.log(error)
return res.status(500).json({error})
}
})
const start = async () => {
try {
await client.connect()
app.listen(PORT, () => {
console.log(`Server is connected to redis and is listening on port ${PORT}`)
})
} catch (error) {
console.log(error)
}
}
start()
现在运行服务器,让我们测试我们的端点:
击中此端点时,您会注意到的第一件事是获得响应需要多长时间。正如我之前提到的,这是有意的,它是为了模拟未实施CACHIND时CPU强度的现实应用程序。
接下来,您会注意到响应中的第一个属性,即isCached
读取false
。这意味着我们的缓存中不存在此数据,但已立即添加。我们如何确认?好吧,让我们简单地刷新浏览器来再次提出相同的请求。这就是我们得到的:
请注意,当您不断刷新页面时,响应时间会减少?另外,请注意,isCached
Property现在读取true
。
要测试我们更改数据库时会发生什么,我邀请您使用以下任何端点来创建,编辑或删除用户来修改响应:
创建用户(帖子):https://pleasant-newt-girdle.cyclic.app/api/user
更新用户(put)/删除用户(删除):https://pleasant-newt-girdle.cyclic.app/api/user/:id
每次您都成功地(发布,put和删除)isCached
值变为false
,促使延迟响应以获取当前数据并更新缓存。请记住,您总是可以在http://localhost:7000/get-users
上刷新端点,以可视化发生的变化。
这是如何在Postman上提出这些请求的示例。
创建新用户
更新用户
删除用户
现在,如果出于某种原因,API,https://pleasant-newt-girdle.cyclic.app/api/user
将来不再存在,您也可以尝试使用类似的公共API https://reqres.in/api/users?delay=3
。以下是它的工作方式:API的响应时间取决于查询参数delay
。这意味着,如果您设置了delay=3
,则API返回响应需要三秒钟。这意味着您可以使用此API练习缓存机制。
但是,等等,我们如何真正可视化缓存中的数据?好吧,您可以下载一个工具,例如 redisinsight ,连接数据库实例,瞧,您现在可以可视化和查询缓存。
这是GitHub上完整代码的链接。谢谢您坚持到最后。
最终注释:
恭喜,如果您走了这么远。到现在为止,您应该对如何将Redis作为强大的缓存解决方案有深入的了解。但是,使用REDIS的缓存不仅可以加速数据检索。在最后的说明中,让我们探索一些其他用例并讨论缓存的出色实用性。
会话缓存:redis是存储会话数据的绝佳选择。通过缓存会话信息,您可以实现高性能会话管理,改善用户体验并减少数据库负载。 REDIS在密钥上设置到期时间的能力使其非常适合管理会话超时并自动清理过期的会话。
全页缓存:REDIS可用于缓存整个HTML页面,从而消除了按每个请求重新生成的需求。通过直接从Redis提供缓存页面,您可以大大减少响应时间并减轻应用程序服务器的负载。
结果缓存:REDIS使您可以缓存复杂或耗时的计算结果。例如,如果您的应用程序涉及大量计算或数据处理,则可以将计算结果存储在Redis缓存中并在需要时检索它们,避免使用冗余计算。
排行榜和计数器:Redis的分类集和原子增量操作使其成为实施排行榜,投票柜台或受欢迎程度排名的绝佳选择。通过缓存这些经常更改的指标,您可以有效地更新并实时显示它们。
pub/sub Messaging :Redis支持出版/订阅(Pub/sub)消息传递,允许您构建实时通信渠道,通知和事件驱动的体系结构。通过缓存消息或维护订阅列表,REDIS促进了可扩展的高性能消息传递系统的实现。
用redis缓存的有用性不能被夸大。通过智能缓存数据,您可以实现重大的性能提高,减少延迟并增强应用程序的总体可扩展性。但是,在使用缓存系统时,考虑缓存无效并保持数据一致性至关重要。