缓存是什么?
高并发下,一个项目最先出问题的,并不是程序本身,而是数据库最先承受不住。
在数据库上我们可以做很多优化,例如优化 SQL 语句,优化索引,如果数据量大了,还可以分库、分表等等。
一个项目中,大多数使用场景都是查询操作,而不是其他新增、修改和删除。那我们思考一下,能不能在第一次查询后,将数据存储在某个地方。下次再查询的时候,就直接读取之前保存的。如果数据发生改变了,就保存的数据删掉,重新保存最新的。这样不就可以大幅度的减少数据库的查询吗?
缓存
就是这个原理。
Redis 又是什么?
Redis
是一个开源的、基于内存的数据存储系统,可以用作数据库、缓存和消息中间件。Redis
的数据都存储在内存中,这样它的读写速度就非常快。而且还会将内存中的数据写入本地硬盘,可以在服务器重启后,自动将数据恢复到内存中。学习
Redis
是大家成为高手的必经之路。它的功能非常强大,在我们这节课中,只会用到Redis的缓存能力。
如何安装 Redis 的服务端与客户端?
1、安装 Redis 服务端 Windows Docker Desktop安装及使用 Docker 运行 MySQL
运行Redis
,最推荐的方式依然是使用Docker
。打开项目根目录的docker-compose.yml
文件,添加Redis
的相关配置。大家注意下格式,缩进非常重要,一定是和之前的MySQL配置对齐的
js
services:
mysql:
# ....
redis:
image: redis:7.4
ports:
- "6379:6379"
volumes:
- ./data/redis:/data
完成后,命令行,再次运行
js
docker-compose up -d
这样Docker
,就会自动下载好Redis
,并在6379
端口启动它。

2、安装 Redis 客户端
服务端启动好了,还需要用客户端进行管理。所谓客户端,就是像 Navicat、Sequel Ace 一样用来管理数据库的工具。这里推荐大家使用的客户端,叫做Redis Insight
,它是 Redis 官方发布的管理工具。它是完全免费的,非常好用。
Redis Insight官网
双击一下,就可以连进去了。
目前里面还没有存任何东西进去,所以都是空白的。好了,先丢一边,一会儿我们再来继续用它。
如何在 Node 项目中,使用 Redis 缓存数据?
Redis
已经安装,并启动好了以后,现在需要在Node
项目中来操作它。
1、安装 node-redis
项目根目录
js
npm i redis
2、Redis 配置文件
接着看官方文档

- 这里告诉我们了,使用前,要先用
connect
方法连接上去。 - 用
set
来写入数据。 - 用
get
来读取数据。 key
,就是给缓存数据,取了个名字。无论写入,还是读取,都是用这个名字,就能找到它。- 不用了以后,可以用
disconnect
方法来断开连接。
新建utils/redis.js
,内容我已经写好了
js
const {createClient} = require('redis');
// 创建全局的 Redis 客户端实例
let client;
/**
* 初始化 Redis 客户端
*/
const redisClient = async () => {
if (client) return; // 如果客户端已经初始化,则不再重复初始化
client = await createClient()
.on('error', err => console.log('Redis 连接失败', err))
.connect();
};
/**
* 存入数组或对象,并可选地设置过期时间
* @param key 键名
* @param value 要存储的值
* @param ttl 可选,以秒为单位的过期时间,默认不设置
*/
const setKey = async (key, value, ttl = null) => {
if (!client) await redisClient(); // 确保客户端已初始化
value = JSON.stringify(value); // 将对象转换为JSON字符串
await client.set(key, value);
// 如果提供了ttl,则设置过期时间
if (ttl !== null) {
await client.expire(key, ttl);
}
};
/**
* 读取数组或对象
* @param key 键名
* @returns {Promise<any>} 解析后的JSON对象或数组
*/
const getKey = async (key) => {
if (!client) await redisClient(); // 确保客户端已初始化
const value = await client.get(key); // 将获取到的JSON字符串转换回对象
return value ? JSON.parse(value) : null; // 如果value为空,返回null而不是抛出错误
};
/**
* 清除缓存数据
* @param key
* @returns {Promise<void>}
*/
const delKey = async (key) => {
if (!client) await redisClient(); // 确保客户端已初始化
await client.del(key);
};
/**
* 获取匹配模式的所有键名
* @param pattern
* @returns {Promise<*>}
*/
const getKeysByPattern = async (pattern) => {
if (!client) await redisClient();
return await client.keys(pattern);
}
/**
* 清空所有缓存数据
* @returns {Promise<void>}
*/
const flushAll = async () => {
if (!client) await redisClient();
await client.flushAll();
}
module.exports = {redisClient, setKey, getKey, delKey, getKeysByPattern, flushAll};
- 先定义了一个
client
变量,里面会创建全局的Redis
客户端实例。 - 第一个方法,是用来连接到了
Redis
的。这里有一个判断,如果已经存在了,就直接返回,这样可以避免重复的创建 Redis 实例。 - 接着我们自定义了写入数据的方法,需要注意的是,在存储之前,先要用
JSON.stringify()
方法将 JS 对象或数组转换成JSON 字符串
。 - 下面还有过期时间的设置,不传就是永久的。如果传了,以秒为单位,如果时间超出后,
Redis
会自动清理掉存储的数据。 - 读取方法里,使用
JSON.parse()
方法将JSON 字符串解析回原来的数据结构。 - 最下面是删除缓存的方法,调用
del
方法,即可删掉缓存数据。
这样做好封装好后,用起来就非常简单了,而且就算是数组或对象,也可以顺利的存取了。
PS: 如果您的 Redis 服务端,运行在其他服务器上或者你在宝塔中不想使用docker,可以调整连接格式为:
js
client = await createClient({
url: 'redis://<服务器IP>:<端口号>' ,
// password:'', 有无密码
})

3、通过utils/redis.js
使用
js
// 引入
const { setKey, getKey } = require('../utils/redis');
// 单条数据存储 key:键名 data:数据
setKey(key, data);
// 带有效期存储 key:键名 data:数据 ttl:时间s为单位
setKey( key, data, ttl);
// 带分页的缓存 articles:默认为路由名 后用:链接 当前页和当页数量 (预定俗称以:为连接)
const cacheKey = `articles:${currentPage}:${pageSize}`;
setKey(cacheKey, data);
// 带id 的缓存 articles:默认为路由名 后用:链接 当前id (预定俗称以:为连接)
setKey(`article:${id}`, data)
// 单条数据删除 key:键名
await delKey(key);
// 复杂缓存删除
/**
* 清除缓存
* @param id
* @returns {Promise<void>}
*/
async function clearCache(id = null) {
// 清除所有文章列表缓存
let keys = await getKeysByPattern('articles:*');
if (keys.length !== 0) {
await delKey(keys);
}
// 如果传递了id,则通过id清除文章详情缓存
if (id) {
// 如果是数组,则遍历
const keys = Array.isArray(id) ? id.map(item => `article:${item}`) : `article:${id}`;
await delKey(keys);
}
}