Skip to content

S08-99 Node-扩展-Redis

[TOC]

概述

Redis(Remote Dictionary Server,远程字典服务):是一个开源的、基于内存的 键值对存储系统。它是一个非关系型数据库(NoSQL)。有以下两个最关键的特点:

  1. 基于内存

    数据主要存储在内存(RAM)中,这使得它的读写速度非常快,通常能达到微秒级别。相比之下,传统数据库(如 MySQL)需要读写硬盘,速度要慢得多。

  2. 键值存储

    使用简单的键值对模型来存储数据。


核心特性

除了“快”,Redis 还有以下几个关键特性,使其功能远超一个简单的缓存工具:

  1. 丰富的数据结构 这是 Redis 与许多其他键值数据库最大的不同。它支持的值不仅仅是字符串,还包括:

    • String(字符串)

      最基本的数据类型,可以存储文本、数字甚至二进制数据。

    • List(列表)

      一个简单的字符串列表,按插入顺序排序。你可以从列表的左端或右端添加、移除元素,实现栈或队列的功能。

    • Set(集合)

      一个无序的、不重复的字符串集合。可以用来求交集、并集、差集等。

    • Sorted Set(有序集合)

      类似于 Set,但每个元素都会关联一个分数(Score),可以根据分数进行排序。非常适合做排行榜。

    • Hash(哈希)

      类似于编程语言中的 Map,适合存储对象(例如,存储一个用户的 nameageemail 等字段)。

    • Bitmaps(位图)HyperLogLogs 等:

      用于处理特定场景,如统计活跃用户。

  2. 持久化 虽然数据存储在内存中,但 Redis 提供了两种机制将内存中的数据保存到硬盘上,以防止服务器重启或宕机时数据丢失:

    • RDB(快照)

      在指定的时间间隔内,将内存中的数据生成一个完整的快照文件。

    • AOF(追加日志)

      记录每一次写操作命令,并在重启时重新执行这些命令来恢复数据。

  3. 高可用和分布式(Redis Sentinel 和 Redis Cluster)

    • 主从复制

      数据可以从一个主节点复制到多个从节点,实现数据备份和读写分离。

    • Redis Sentinel(哨兵)

      提供自动的主节点故障转移功能,当主节点宕机时,能自动将一个从节点升级为新的主节点,保证服务高可用。

    • Redis Cluster(集群)

      提供数据分片功能,可以将数据分布到多个节点上,突破单机内存和性能的限制。


Redis 的优缺点

优点

  • 极快的速度:内存访问,单机可达 10万+ QPS。
  • 丰富的数据结构:使编程模型非常灵活。
  • 原子性操作:所有操作都是原子的,避免了并发问题。
  • 多功能性:可用于缓存、消息队列、数据库等多种角色。
  • 强大的生态系统:支持持久化、主从复制、高可用和分片集群。

缺点

  • 容量受内存限制:数据量不能超过服务器内存容量(虽然有集群方案,但成本较高)。
  • 持久化开销:RDB 生成快照和 AOF 重写时,可能会对性能产生一定影响。
  • 不适合存储冷数据:因为内存昂贵,存储大量不常访问的数据成本太高。

安装

Windows【

Docker

使用 Docker 安装(推荐,跨平台)

这是最干净、最简单的方式,无需处理依赖问题。

  1. 拉取 Redis 镜像

    sh
    docker pull redis:latest
  2. 运行 Redis 容器

    sh
    docker run --name my-redis -p 6379:6379 -d redis
    • --name my-redis:给容器起一个名字,方便管理。
    • -p 6379:6379:将主机的 6379 端口映射到容器的 6379 端口(Redis 默认端口)。
    • -d:在后台运行容器。
  3. 验证

    运行 docker ps,如果看到名为 my-redis 的容器正在运行,说明安装成功。

Linux

在 Linux 上安装(以 Ubuntu 为例)

  1. 更新软件包列表并安装

    sh
    sudo apt update
    sudo apt install redis-server
  2. 启动 Redis 服务

    sh
    sudo systemctl start redis-server
  3. 设置开机自启

    sh
    sudo systemctl enable redis-server
  4. 验证服务是否运行

    如果看到 active (running) 的字样,说明 Redis 正在运行。

    sh
    sudo systemctl status redis-server

MacOS

在 macOS 上安装

使用 Homebrew 可以轻松安装。

  1. 安装 Homebrew(如果尚未安装):访问 brew.sh 获取安装命令。

  2. 使用 Homebrew 安装 Redis

    sh
    brew install redis
  3. 启动 Redis 服务

    • 方式一(前台运行):直接在终端输入 redis-server

    • 方式二(后台服务):使用 Homebrew 服务管理。

      sh
      brew 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 测试连接

sh
127.0.0.1:6379> PING # 测试连接
PONG # 返回 PONG 表示连接正常

Key 相关通用命令

sh
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 字符串

最基础的类型,可以存文本数字等。

sh
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 列表

按插入顺序排序的字符串列表,可以从左右两端操作。

sh
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 集合

无序不重复的字符串集合。

sh
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),用于排序。

sh
# 添加成员和分数
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 哈希

适合存储对象。

sh
# 设置一个用户对象
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 设置一个生存时间,过期后自动删除。这在做缓存时非常有用。

sh
# EX 后跟秒数,表示 300 秒后过期
127.0.0.1:6379> SET access_token "abc123" EX 300
OK

# 查看剩余生存时间
127.0.0.1:6379> TTL access_token
(integer) 297

退出

sh
127.0.0.1:6379> QUIT # 退出 redis-cli

Node 操作 Redis

连接配置

依赖包


本地配置

src/conf/db.js 中添加 redis 的连接配置并导出

image-20251011153551442


线上配置

1、区分环境:终端脚本运行时传递参数 NODE_ENV 用于区分运行环境

image-20251011154128043

2、node 中判断环境:在 node 中可以通过 process.env.NODE_ENV 接收终端传递的参数

image-20251011154503228

3、配置线上连接

image-20251011154744159

事件

注意必须监听 error 事件,否则当遇到错误时 node 进程会退出。

image-20251011155600887

连接 redis

  • redis.createClient()(port?, host?),创建 redis 客户端。
  • client.on()(eventType, callback)事件监听。
  • client.connect()(),连接 Redis 数据库。

image-20251011155129145

封装方法

封装 set() 方法

image-20251011160327444


封装 get() 方法

image-20251011160850506

缓存

数据库缓存

js
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

  1. 浏览器在访问网站服务器时,会在cookie中携带自己的 userid。
  2. 服务器通过该 userid 识别用户的身份
  3. 服务器通过该 userid 去 session 中获取到用户的信息并返回给浏览器

image-20251011162053652


session 默认存储位置

session 默认是存储在服务器内存中的。这会导致以下的问题:

  • 服务器内存使用量大增

  • 不能持久化,服务器进程结束,session 信息也会随之丢失。

  • 操作系统会限制一个进程的最大可用内存(约1个多G),如果开启多个进程,进程之间无法获取对方存储的 session 信息。

    image-20251011162949800


redis 缓存 session

使用 redis 缓存 session 可以解决以上的问题:

  1. 用户通过浏览器访问 web 服务器时,其实是访问多个服务器组成的集群中的某个服务器。
  2. 该服务器此时会根据用户的 userid 从 redis 中获取存储的 session 信息,并返回给浏览器。

image-20251011163110487


为何 session 适合用 redis

  • session 访问频繁,对性能要求极高,redis 运行在内存中符合该要求。
  • session 可以不考虑断电数据丢失的问题,redis 的该缺点对 session 没有影响。
  • session 数据量不会太大

为何网站数据不适合用 redis

  • 操作频率相比 session 不是太高,性能要求相对也没有那么高。
  • 断电数据不能丢失。
  • 数据量太大,内存成本高。

koa2 配置 session

安装依赖

sh
pnpm i koa-redis koa-generic-session

配置 session

1、在 app.js 中的路由注册之前注册 session

image-20251011165926690

2、在路由中通过 ctx.session 访问 session

  • 注意:只有使用了 session 之后,cookie 中才会携带 sessionID。

image-20251011170556965

3、此时 cookie 中会携带 sessionID

image-20251011170457963

4、redis 数据库中保存了 session 信息,key 就是 cookie 中携带的 sessionID

image-20251011170802289

5、通过 get <key> 可以获取到对应的值

image-20251011171027150