使用NodeJS的Redis和缓存的初学者指南

1,125 阅读7分钟

NodeJS的Redis和缓存的初级指南

近年来,Redis已经成为Node.js应用程序堆栈中的一个常见现象。虽然它最受欢迎的用例是缓存,但Redis还有许多其他用例,你可以利用其极快的内存数据库的优势。

在本教程中,我们将向你快速介绍Redis。我们还将使用Redis为一个Node应用程序创建一个简单的缓存,看看它是如何影响其性能的。


什么是Redis?

Redis是一个开源(BSD许可)的内存数据结构存储,被用作数据库、缓存和消息代理。

你可以把它看作是一个No-SQL数据库,它把数据作为键值对存储在系统内存中。如果需要,Redis也支持磁盘持久化数据存储。

Redis支持存储多种数据结构和数据类型,包括字符串、列表、哈希值、集合和排序集合。支持的数据结构使Redis在许多使用情况下具有多样性。

Redis最适合于需要在最短的时间内检索数据并交付给客户端的情况。


Redis的使用案例

用于缓存的Redis

Redis最受欢迎的使用情况之一是缓存。

什么是缓存?

缓存是在缓存中存储数据副本的过程,使应用程序能够更快地访问和检索数据。缓存的目标是加快数据访问操作的速度,比数据库或远程服务器所允许的要好。对于昂贵的(时间上的)操作来说更是如此。

作为一个后端开发者,我们的任务是尽可能快地完成客户的请求。有时,查询需要几个操作,如从数据库中检索数据,执行计算,从其他服务中检索额外的数据等,这些都会拖累我们的性能。

这就是缓存的优势所在,因为我们可以对数据进行一次处理,将其存储在缓存中,然后稍后直接从缓存中检索,而无需进行所有这些昂贵的操作。然后我们会定期更新缓存,这样用户就能看到最新的信息。

缓存和Redis

由于Redis是一个内存数据库,它的数据访问操作比任何其他绑定磁盘的数据库都要快。这使得Redis成为缓存的完美选择。它的键值数据存储是另一个优点,因为它使数据存储和检索更简单。

在本教程中,我们将看到如何用Redis和Node.js进行缓存。

用于实时分析的Redis

Redis承诺了亚毫秒级的数据处理操作。这使得Redis成为依赖实时数据分析的应用程序的完美人选。

例如,在实现实时欺诈检测服务时,你可以使用Redis来存储用户身份和他们的交易细节。Redis甚至提供了一个由人工智能支持的更快的交易评分系统和更快的统计模型,以更好地执行这一用例。

实时分析的其他用例包括实时库存管理系统和游戏排行榜。

Redis用于会话管理

如果你的应用程序使用会话来跟踪认证的用户并管理用户的特定数据,Redis是一个完美的选择,可以用作会话存储。使用Redis可以显著提高系统的性能,同时更容易处理用户的数据,包括凭证、最近的活动,甚至是类似购物车的系统。

Redis作为一个队列

你可以用Redis来排队完成那些需要很长时间的应用任务。你可以实现FIDO(先进先出)队列,或者创建延迟队列,将任务执行推迟到预先安排的时间。


用Node和Redis进行缓存

现在,让我们开始讨论本教程的主要焦点:在NodeJS应用程序中使用Redis进行缓存。

使用Redis进行缓存的过程非常简单。当我们收到用户对已启用缓存的路由的请求时,我们首先检查请求的数据是否已经存储在缓存中。如果是,我们可以快速从Redis中获取数据并发送响应。

然而,如果数据没有存储在缓存中,我们称之为缓存缺失,我们必须首先从数据库或外部API中检索数据,并将其发送给客户端。我们还要确保将检索到的数据存储在缓存中,以便下次收到相同的请求时,我们可以简单地将缓存的数据更快地发送给用户。

现在你对我们要做的事情有了清楚的认识,让我们开始实施。


安装Redis

如果你还没有,你需要为这个教程安装Redis。

你可以下载二进制文件并使用以下命令轻松编译:

wget https://download.redis.io/releases/redis-6.0.9.tar.gz
tar xzf redis-6.0.9.tar.gz
cd redis-6.0.9
make
make install

为了确保Redis服务器的运行没有问题,使用redis-cli ,向服务器发送一个ping:

redis-cli ping

如果你收到pong 作为响应,说明Redis服务器运行成功。

构建NodeJS应用程序

基本设置

像这样为Node应用程序设置初始模板:

const express = require("express");
const axios = require("axios");
const redis = require("redis");
const app = express();

app.listen(process.env.PORT || 3000, () => {
    console.log("Node server started");
});

注意我们如何使用两个额外的包,名为axiosredisredis 是Node的标准Redis客户端。在本教程中,我们使用axios ,从一个外部API中检索数据。

在继续之前,请确保使用npm安装这两个包:

npm install axios redis --save

从外部API检索数据

我们将使用GitHub Jobs API来获取与世界上不同地点的编程工作相关的数据。

你可以向API传递一个与你要找的工作有关的搜索词,并检索出json格式的可用工作数组。对API的请求样本是这样的。

https://jobs.github.com/positions.json?search=node.js

POSTMAN - GitHub 工作 API 结果

在我们的Node应用程序中,我们定义了一个名为/jobs 的路由,它从上述API中检索工作数据,并将它们发送回客户端:

const express = require("express");
const axios = require("axios");
const redis = require("redis");
const app = express();

app.get("/jobs", async (req, res) => {
    const searchTerm = req.query.search;
    try {
        const jobs = await axios.get(`https://jobs.github.com/positions.json?search=${searchTerm}`);
        res.status(200).send({
            jobs: jobs.data,
        });	
    } catch(err) {
        res.status(500).send({message: err.message});
    }
});

app.listen(process.env.PORT || 3000, () => {
    console.log("Node server started");
});

在这里,我们使用axios ,用用户提供的搜索词向GitHub Jobs API发送一个GET请求。

让我们看看现在使用Postman的路由是如何工作的:

POSTMAN - 我们的API结果

缓存结果

现在,让我们看看如何通过缓存提高应用程序的性能。

首先,我们需要通过我们的应用程序连接到Redis服务器。我们使用已安装的redis包来完成这项任务。

const redisPort = 6379
const client = redis.createClient(redisPort);

//log error to the console if any occurs
client.on("error", (err) => {
    console.log(err);
});

Redis服务器默认监听端口为6379。因此,我们传递端口号来连接Redis并创建一个客户端。

然后,实现逻辑,从缓存中存储和检索数据:

app.get("/jobs", (req, res) => {
    const searchTerm = req.query.search;
    try {
        client.get(searchTerm, async (err, jobs) => {
            if (err) throw err;
    
            if (jobs) {
                res.status(200).send({
                    jobs: JSON.parse(jobs),
                    message: "data retrieved from the cache"
                });
            } else {
                const jobs = await axios.get(`https://jobs.github.com/positions.json?search=${searchTerm}`);
                client.setex(searchTerm, 600, JSON.stringify(jobs.data));
                res.status(200).send({
                    jobs: jobs.data,
                    message: "cache miss"
                });
            }
        });
    } catch(err) {
        res.status(500).send({message: err.message});
    }
});

这里发生了什么?

当我们收到客户端对/jobs路由的请求时,首先,我们得到与请求的查询参数一起发送的搜索词:

const searchTerm = req.query.search;

然后,我们试图通过搜索词从缓存中检索所请求的数据,在缓存中存储数据时,我们将搜索词作为密钥。由于Redis包没有对承诺的原生支持,我们必须通过一个回调来处理检索到的数据:

client.get(searchTerm, async (err, jobs) => {
    if (err) throw err;
});

如果从Redis返回的值不是空的,这意味着相关的数据存在于缓存中,所以很容易在响应中返回这些数据。只要确保你把字符串投回给JSON:

if (jobs) {
    res.status(200).send({
        jobs: JSON.parse(jobs),
        message: "data retrieved from the cache"
    });
}

如果返回的值是空的,我们必须向外部API发送请求以检索相关数据:

else {
    const jobs = await axios.get(`https://jobs.github.com/positions.json?search=${searchTerm}`);
     client.setex(searchTerm, 600, JSON.stringify(jobs.data));
     res.status(200).send({
          jobs: jobs.data,
          message: "cache miss"
     });
}

当我们从API获得数据时,在发回数据之前,我们将其存储在Redis中,这样下次向Node服务器发送相同的请求时,它可以用存储在缓存中的数据进行响应,而不是从API中请求它们。

注意我们是如何使用setex 函数来存储缓存中的数据的。特别是使用setex 函数,而不是常规的set 函数,我们可以为存储的键值对设置一个过期时间。因为我们为过期时间设置了一个值,所以当过期时间过后,Redis会自动从缓存中删除该键值对。


完整的源代码

const express = require("express");
const axios = require("axios");
const redis = require("redis");
const app = express();

const redisPort = 6379
const client = redis.createClient(redisPort);

client.on("error", (err) => {
    console.log(err);
})

app.get("/jobs", (req, res) => {
    const searchTerm = req.query.search;
    try {
        client.get(searchTerm, async (err, jobs) => {
            if (err) throw err;
    
            if (jobs) {
                res.status(200).send({
                    jobs: JSON.parse(jobs),
                    message: "data retrieved from the cache"
                });
            }
            else {
                const jobs = await axios.get(`https://jobs.github.com/positions.json?search=${searchTerm}`);
                client.setex(searchTerm, 600, JSON.stringify(jobs.data));
                res.status(200).send({
                    jobs: jobs.data,
                    message: "cache miss"
                });
            }
        });
    } catch(err) {
        res.status(500).send({message: err.message});
    }
});

app.listen(process.env.PORT || 3000, () => {
    console.log("Node server started");
});

就这样了。我们已经为我们的应用程序创建了一个简单的缓冲区。这并不难,不是吗?


关键时刻:时间比较

我们来看看使用缓存对我们的应用程序的性能有何影响。我使用Postman向服务器发送请求并测量请求完成时间。

性能

当你第一次向服务器发送一个新的搜索词的请求时,应用程序需要更长的时间来响应(超过7秒),因为它必须从外部API获得数据。当你第二次发出同样的请求时,服务器的响应速度更快,因为结果已经存在于缓存中。

该请求在10毫秒内完成。与我们之前看到的没有缓存的应用相比,这是一个巨大的性能提升。


总结

在本教程中,我们对Redis进行了快速介绍,并为Node.js应用程序创建了一个简单的缓存。现在你可以使用Redis来缓存你的应用程序中经常查询的数据,以获得可观的性能提升。

你也可以研究一下如何在其他用例中利用Redis的最佳功能。

谢谢你的阅读!