S08-99 Node-扩展-Redis
[TOC]
概述
Redis(Remote Dictionary Server,远程字典服务):是一个开源的、基于内存的 键值对存储系统。它是一个非关系型数据库(NoSQL)。有以下两个最关键的特点:
基于内存:
数据主要存储在内存(RAM)中,这使得它的读写速度非常快,通常能达到微秒级别。相比之下,传统数据库(如 MySQL)需要读写硬盘,速度要慢得多。
键值存储:
使用简单的键值对模型来存储数据。
核心特性:
除了“快”,Redis 还有以下几个关键特性,使其功能远超一个简单的缓存工具:
丰富的数据结构 这是 Redis 与许多其他键值数据库最大的不同。它支持的值不仅仅是字符串,还包括:
String(字符串):
最基本的数据类型,可以存储文本、数字甚至二进制数据。
List(列表):
一个简单的字符串列表,按插入顺序排序。你可以从列表的左端或右端添加、移除元素,实现栈或队列的功能。
Set(集合):
一个无序的、不重复的字符串集合。可以用来求交集、并集、差集等。
Sorted Set(有序集合):
类似于 Set,但每个元素都会关联一个分数(Score),可以根据分数进行排序。非常适合做排行榜。
Hash(哈希):
类似于编程语言中的 Map,适合存储对象(例如,存储一个用户的
name
,age
,email
等字段)。Bitmaps(位图) 和 HyperLogLogs 等:
用于处理特定场景,如统计活跃用户。
持久化 虽然数据存储在内存中,但 Redis 提供了两种机制将内存中的数据保存到硬盘上,以防止服务器重启或宕机时数据丢失:
RDB(快照):
在指定的时间间隔内,将内存中的数据生成一个完整的快照文件。
AOF(追加日志):
记录每一次写操作命令,并在重启时重新执行这些命令来恢复数据。
高可用和分布式(Redis Sentinel 和 Redis Cluster)
主从复制:
数据可以从一个主节点复制到多个从节点,实现数据备份和读写分离。
Redis Sentinel(哨兵):
提供自动的主节点故障转移功能,当主节点宕机时,能自动将一个从节点升级为新的主节点,保证服务高可用。
Redis Cluster(集群):
提供数据分片功能,可以将数据分布到多个节点上,突破单机内存和性能的限制。
Redis 的优缺点:
优点:
- 极快的速度:内存访问,单机可达 10万+ QPS。
- 丰富的数据结构:使编程模型非常灵活。
- 原子性操作:所有操作都是原子的,避免了并发问题。
- 多功能性:可用于缓存、消息队列、数据库等多种角色。
- 强大的生态系统:支持持久化、主从复制、高可用和分片集群。
缺点:
- 容量受内存限制:数据量不能超过服务器内存容量(虽然有集群方案,但成本较高)。
- 持久化开销:RDB 生成快照和 AOF 重写时,可能会对性能产生一定影响。
- 不适合存储冷数据:因为内存昂贵,存储大量不常访问的数据成本太高。
安装
Windows【
Docker
使用 Docker 安装(推荐,跨平台):
这是最干净、最简单的方式,无需处理依赖问题。
拉取 Redis 镜像:
shdocker pull redis:latest
运行 Redis 容器:
shdocker run --name my-redis -p 6379:6379 -d redis
--name my-redis
:给容器起一个名字,方便管理。-p 6379:6379
:将主机的 6379 端口映射到容器的 6379 端口(Redis 默认端口)。-d
:在后台运行容器。
验证:
运行
docker ps
,如果看到名为my-redis
的容器正在运行,说明安装成功。
Linux
在 Linux 上安装(以 Ubuntu 为例):
更新软件包列表并安装:
shsudo apt update sudo apt install redis-server
启动 Redis 服务:
shsudo systemctl start redis-server
设置开机自启:
shsudo systemctl enable redis-server
验证服务是否运行:
如果看到
active (running)
的字样,说明 Redis 正在运行。shsudo systemctl status redis-server
MacOS
在 macOS 上安装:
使用 Homebrew 可以轻松安装。
安装 Homebrew(如果尚未安装):访问 brew.sh 获取安装命令。
使用 Homebrew 安装 Redis:
shbrew install redis
启动 Redis 服务:
方式一(前台运行):直接在终端输入
redis-server
。方式二(后台服务):使用 Homebrew 服务管理。
shbrew services start redis
基本使用
连接 Redis 服务器
使用 redis-cli
(Redis 命令行接口)工具来连接和操作服务器。
本机连接:
sh# 如果 Redis 运行在本机默认端口 redis-cli
Docker 容器中连接:
sh# 如果运行在 Docker 容器中,需要连接到容器的 IP 和端口 # 或者直接进入容器内部操作 docker exec -it my-redis redis-cli
远程连接:
sh# 如果连接远程或指定端口的 Redis redis-cli -h host -p port -a password
基本命令
注意:命令不区分大小写,但是推荐使用大写。
PING 测试连接
127.0.0.1:6379> PING # 测试连接
PONG # 返回 PONG 表示连接正常
Key 相关通用命令
127.0.0.1:6379> SET mykey "Hello" # 设置一个键值对
OK
127.0.0.1:6379> GET mykey # 获取一个键的值
"Hello"
127.0.0.1:6379> EXISTS mykey # 检查键是否存在
(integer) 1
127.0.0.1:6379> DEL mykey # 删除一个键
(integer) 1
127.0.0.1:6379> KEYS my* # 查找所有以 ‘my’ 开头的键
1) "mykey"
数据类型
String 字符串
最基础的类型,可以存文本、数字等。
127.0.0.1:6379> SET username "alice" # 设置字符串
OK
127.0.0.1:6379> GET username # 获取
"alice"
127.0.0.1:6379> SET counter 100 # 设置数字
OK
127.0.0.1:6379> INCR counter # 自增 1 -> 101
(integer) 101
127.0.0.1:6379> INCRBY counter 10 # 增加 10 -> 111
(integer) 111
List 列表
按插入顺序排序的字符串列表,可以从左右两端操作。
127.0.0.1:6379> LPUSH mylist "world" # 从左边插入
(integer) 1
127.0.0.1:6379> LPUSH mylist "hello"
(integer) 2
127.0.0.1:6379> RPUSH mylist "!" # 从右边插入
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1 # 获取列表所有元素,LRANGE KEY_NAME START END,负数表示从后开始数
1) "hello"
2) "world"
3) "!"
127.0.0.1:6379> LPOP mylist # 从左边弹出一个元素
"hello"
Set 集合
无序、不重复的字符串集合。
127.0.0.1:6379> SADD tags "redis" "database" "cache" # 添加元素,元素间使用空格分隔
(integer) 3
127.0.0.1:6379> SMEMBERS tags # 获取所有成员(无序)
1) "cache"
2) "database"
3) "redis"
127.0.0.1:6379> SADD tags "redis" # 添加重复元素,失败
(integer) 0
127.0.0.1:6379> SISMEMBER tags "redis" # 检查成员是否存在
(integer) 1
Sorted Set 有序集合
类似 Set,但每个成员都有一个分数(score),用于排序。
# 添加成员和分数
127.0.0.1:6379> ZADD leaderboard 100 "player1" 200 "player2" 150 "player3"
(integer) 3
# 按分数升序获取(0到-1表示所有)-Z RANGE
127.0.0.1:6379> ZRANGE leaderboard 0 -1
1) "player1"
2) "player3"
3) "player2"
# 按分数降序获取(带分数)-Z REV RANGE
127.0.0.1:6379> ZREVRANGE leaderboard 0 -1 WITHSCORES
1) "player2"
2) "200"
3) "player3"
4) "150"
5) "player1"
6) "100"
Hash 哈希
适合存储对象。
# 设置一个用户对象
127.0.0.1:6379> HSET user:1000 name "Bob" age 30 email "bob@example.com"
(integer) 3
# 获取整个对象 H GET ALL
127.0.0.1:6379> HGETALL user:1000
1) "name"
2) "Bob"
3) "age"
4) "30"
5) "email"
6) "bob@example.com"
# 获取单个字段
127.0.0.1:6379> HGET user:1000 name
"Bob"
# 增加年龄
127.0.0.1:6379> HINCRBY user:1000 age 1
(integer) 31
数据过期时间
Redis 可以给 Key 设置一个生存时间,过期后自动删除。这在做缓存时非常有用。
# EX 后跟秒数,表示 300 秒后过期
127.0.0.1:6379> SET access_token "abc123" EX 300
OK
# 查看剩余生存时间
127.0.0.1:6379> TTL access_token
(integer) 297
退出
127.0.0.1:6379> QUIT # 退出 redis-cli
Node 操作 Redis
连接配置
依赖包:
- node-redis:
pnpm i redis
本地配置:
在 src/conf/db.js
中添加 redis 的连接配置并导出
线上配置:
1、区分环境:终端脚本运行时传递参数 NODE_ENV
用于区分运行环境
2、node 中判断环境:在 node 中可以通过 process.env.NODE_ENV
接收终端传递的参数
3、配置线上连接:
事件
注意:必须监听 error
事件,否则当遇到错误时 node 进程会退出。
连接 redis
- redis.createClient():
(port?, host?)
,创建 redis 客户端。 - client.on():
(eventType, callback)
,事件监听。 - client.connect():
()
,连接 Redis 数据库。
封装方法
封装 set() 方法:
封装 get() 方法:
缓存
数据库缓存
import express from 'express';
import { createClient } from 'redis';
const app = express();
const client = createClient();
await client.connect();
// 2. 缓存中间件
function cacheMiddleware(key, expireTime = 300) {
// 2.1 返回函数作为中间件
return async (req, res, next) => {
// 2.2 获取 redis 的 key
const cacheKey = key || req.originalUrl;
try {
// 2.3 从 redis 获取 key 对应的值
const cachedData = await client.get(cacheKey);
// 2.4 如果取到了值:直接返回 JSON.parse 解析过的该值
if (cachedData) {
console.log('从缓存返回数据');
return res.json(JSON.parse(cachedData));
}
// 2.5 如果没有取到值:放行通过后续的中间件向数据库发送请求
// 2.6 重写 res.json() 可以让当前中间件获取到后续中间件从数据库请求到的数据
// 保存原始的 res.json 方法
const originalJson = res.json.bind(res);
// 重写 res.json 方法以拦截响应并缓存
res.json = (data) => {
// 将数据缓存起来
client.set(cacheKey, JSON.stringify(data), {
EX: expireTime
}).catch(console.error);
// 调用原始的 res.json
return originalJson(data);
};
next();
} catch (error) {
console.error('缓存中间件错误:', error);
next();
}
};
}
// 1. 使用缓存中间件
app.get('/api/products', cacheMiddleware('api:products', 600), async (req, res) => {
// 3. 模拟从数据库获取数据(耗时操作)
const products = await fetchProductsFromDB();
res.json(products);
});
app.get('/api/users/:id', cacheMiddleware(), async (req, res) => {
const userId = req.params.id;
const user = await fetchUserFromDB(userId);
res.json(user);
});
Session 缓存
概述
多浏览器访问 session:
- 浏览器在访问网站服务器时,会在cookie中携带自己的 userid。
- 服务器通过该 userid 识别用户的身份
- 服务器通过该 userid 去 session 中获取到用户的信息并返回给浏览器
session 默认存储位置:
session 默认是存储在服务器内存中的。这会导致以下的问题:
服务器内存使用量大增。
不能持久化,服务器进程结束,session 信息也会随之丢失。
操作系统会限制一个进程的最大可用内存(约1个多G),如果开启多个进程,进程之间无法获取对方存储的 session 信息。
redis 缓存 session:
使用 redis 缓存 session 可以解决以上的问题:
- 用户通过浏览器访问 web 服务器时,其实是访问多个服务器组成的集群中的某个服务器。
- 该服务器此时会根据用户的 userid 从 redis 中获取存储的 session 信息,并返回给浏览器。
为何 session 适合用 redis:
- session 访问频繁,对性能要求极高,redis 运行在内存中符合该要求。
- session 可以不考虑断电数据丢失的问题,redis 的该缺点对 session 没有影响。
- session 数据量不会太大。
为何网站数据不适合用 redis:
- 操作频率相比 session 不是太高,性能要求相对也没有那么高。
- 断电数据不能丢失。
- 数据量太大,内存成本高。
koa2 配置 session
安装依赖:
pnpm i koa-redis koa-generic-session
配置 session:
1、在 app.js
中的路由注册之前注册 session
2、在路由中通过 ctx.session
访问 session
- 注意:只有使用了 session 之后,cookie 中才会携带 sessionID。
3、此时 cookie 中会携带 sessionID
4、redis 数据库中保存了 session 信息,key 就是 cookie 中携带的 sessionID
5、通过 get <key>
可以获取到对应的值