Redis 是什么:全栈项目里的内存数据库

1 阅读6分钟

前言

这个博客项目一路写下来,到了数据层:笔记从哪来?我选了 Redis——一个只用了 20 行代码就接进来的"数据库"。但它和之前接触的 MySQL 完全不同:没有表、没有 SQL、数据还在内存里。这一篇把 Redis 讲透:它是什么、为什么快、用在项目的哪个位置、以及那个最容易踩的坑——装了依赖并不等于装好了数据库。

一、Redis 是什么

Redis = REmote DIctionary Server(远程字典服务器)

一句话:一个跑在内存里的 key-value 数据库。没有表结构、不用写 SQL,直接 key:value 存取。

把它和你熟悉的东西放在一起对比,位置一下就清楚了:

localStorageMySQLRedis
数据在哪浏览器里硬盘内存
结构key:value表格 + SQLkey:value(多种类型)
访问方只有你的浏览器所有连服务器的程序所有连服务器的程序
速度—毫秒级微秒级
存活关浏览器还在重启还在重启就没了(默认)

关键认知:Redis 不是"替代 MySQL"的数据库,而是站在 MySQL 前面的加速层。 它存"一段时间内不变的热数据",MySQL 存权威数据。

二、为什么它这么快

三个原因,面试也常问:

  1. 数据在内存——读写不碰硬盘,这是根本原因。
  2. 单线程 + I/O 多路复用——同一时刻只处理一个命令,没有线程切换开销,配合 epoll 同时监听海量连接。反而因此天然"线程安全",多个客户端不会互相干扰。
  3. 数据模型简单——直接操作内存里的数据结构,没有 SQL 解析、没有索引维护。

代价是内存贵、容量小,所以它从来不是主数据库,而是配合主数据库做读写加速(这正是我 README 里记的那个 I/O 瓶颈场景)。

三、5 种核心数据类型

Redis 的灵魂是类型。同一个 key,用不同方法存,底层结构完全不同,存之前先想好用哪种类型:

类型底层结构典型命令典型场景
String 字符串二进制安全字符串set / get / incr缓存、计数器、验证码
Hash 哈希一个 key 里塞多个 field:valuehset / hget / hgetall对象/记录、用户信息
List 列表双向链表lpush / rpop消息队列、最近列表
Set 集合无序去重sadd / sismember点赞用户、标签
ZSet 有序集合每个元素带分数zadd / zrange排行榜、延迟队列

这个项目用的是 Hash:一个 key 叫 notes,里面每个 field 是一条笔记的 ID,value 是笔记的 JSON。这就是"用 Hash 存对象集合"的标准姿势,下面第五节拆给你看。

四、最容易踩的坑:ioredis 只是客户端

项目 package.json 里装的是 ioredis,它是 Redis 的 Node.js 客户端驱动(类似 mysql2 之于 MySQL、mongoose 之于 MongoDB)。

它本身不包含 Redis 服务端。

你写的代码
   │
   ▼
ioredis 驱动 ──TCP:6379──▶ Redis 服务(单独的程序!)
   │
   ▼
服务器进程 / Docker 容器 / WSL 里的 redis-server

所以 lib/redis.js 里这一行:

import Redis from "ioredis";
const redis = new Redis();   // 无参 = 默认连 localhost:6379

如果本机没有 Redis 服务在 6379 端口监听,一执行 hgetall 就会 ECONNREFUSED——驱动找不到服务器。这和装 mysql2 却不开 MySQL 是一个道理。

本地怎么启动(Windows)

Redis 官方不支持 Windows 原生运行,三条路:

方式做法
Docker(最省事)docker run -d -p 6379:6379 redis
WSL在 WSL 里 sudo apt install redis-server && redis-server
社区移植版下载 tporadowski/redis(Windows 原生二进制)

启动后验证:

redis-cli ping   # 返回 PONG 就说明服务起来了

五、项目里的实际用法:20 行代码接进来

看 lib/redis.js 全貌,它就是"数据业务逻辑"层(Next.js 里放在 lib/ 目录):

// node redis 客户端, 驱动
import Redis from "ioredis";
const redis = new Redis();

// 哈希:key 是字符串 ID,值 note 的序列化字符串
const initialData = {
  1702459181837:
    '{"title":"sunt aut","content":"quia et suscipit suscipit recusandae","updateTime":"2023-12-13T09:19:48.837Z"}',
  1702459182837:
    '{"title":"qui est","content":"est rerum tempore vitae sequi sint","updateTime":"2023-12-13T09:19:48.837Z"}',
  1702459188837:
    '{"title":"ea molestias","content":"et iusto sed quo iure","updateTime":"2023-12-13T09:19:48.837Z"}',
};
export async function getAllNotes() {
  const data = await redis.hgetall("notes");
  if (Object.keys(data).length == 0) {
    await redis.hset("notes", initialData);
  }
  return await redis.hgetall("notes");
}

三个设计点逐个讲:

1. 哈希结构:对象集合的存法

notes 在 Redis 里长这样:

notes (一个哈希)
├─ field "1702459181837" → "{\"title\":\"sunt aut\",...}"
├─ field "1702459182837" → "{\"title\":\"qui est\",...}"
└─ field "1702459188837" → "{\"title\":\"ea molestias\",...}"
  • hgetall("notes") → 一次取回哈希里所有 field-value;
  • hset("notes", data) → 传入一个对象,对象键就是 field、值就是 value,和 Redis 哈希数据结构逐一对上。

2. 时间戳当 ID,JSON 当值

  • field 用 Unix 毫秒时间戳当唯一 ID:不需要自增计数器,毫秒级基本不重复,还能看出创建先后。
  • value 是序列化后的 JSON 字符串:对象不能直接存,先 JSON.stringify 成字符串,取回来再 JSON.parse。

3. 惰性初始化:读不到就自动写入种子数据

const data = await redis.hgetall("notes");
if (Object.keys(data).length == 0) {
  await redis.hset("notes", initialData);  // 首次为空,自动播种
}

Redis 是独立的外部服务,不跟着项目走——换台机器、清掉数据,notes 就空了。所以代码做了一层兜底:读不到就写入初始数据(种子数据),保证任何环境第一次跑都有数据显示。

4. 谁在消费它

components/Sidebar.js 里,服务端组件直接拿:

import { getAllNotes } from "@/lib/redis";

export default async function Sidebar() {
  const notes = await getAllNotes();  // 服务器上直连 Redis,不走 HTTP
  return <SidebarNoteList notes={notes} />;
}

链路是:服务端组件 → lib/redis.js → ioredis → Redis 服务 → 返回数据 → 渲染成 HTML 发给浏览器。Redis 全程只跟 Node 进程打交道,浏览器接触不到——这就是"后端服务",和前几篇讲的 SSR 是同一件事。

六、真实项目里的分工:Redis 站在 MySQL 前面

个人练习 20 行够了,真实项目 Redis 的姿势是这样的:

用户请求文章列表
   ↓
① 先读 Redis:GET notes
   ├─ 有 → 直接返回(命中缓存,不动 MySQL)
   └─ 没有 ↓
② 查 MySQL(权威数据)
③ 写进 Redis,并设过期时间 EXPIRE 300(5 分钟)
④ 返回给用户

要点:

  • 过期时间(TTL):Redis 数据默认永不过期,缓存一定要 expire,否则旧数据永远不刷新。
  • 缓存穿透兜底:Redis 崩了,顶多回源查一次 MySQL,不会丢数据——所以缓存要能"容忍丢失"。
  • 持久化:默认数据在内存、重启就丢。要持久化用 RDB(定期快照,性能好)或 AOF(记录每条写命令,更可靠)。缓存场景一般不需要。

小结

  • Redis 是内存型 key-value 数据库,是 MySQL 前面的加速层,不是替代品
  • 快的原因:数据在内存 + 单线程 I/O 多路复用 + 模型简单
  • 5 种类型各有各的用法,对象集合用 Hash,命令 hset / hgetall
  • ioredis 只是客户端驱动,本机必须另有 Redis 服务在 6379 端口跑,否则 ECONNREFUSED
  • lib/redis.js 的核心模式:哈希存对象集合 + 时间戳当 ID + JSON 当值 + 惰性初始化种子数据
  • 真实项目套路:读 Redis → 没命中查 MySQL → 写回 Redis 并设过期时间