用 Redis 当数据库?Next.js 笔记系统的数据层设计
别人都用 MySQL,这个项目却拿 Redis 存笔记。聊聊 NOSQL 的取舍、hash 类型的妙用,以及 Redis + MySQL 的经典缓存思路。
一、为什么敢用 Redis 存数据
传统认知里,Redis 只是「缓存」,真正的数据要落 MySQL。但这个笔记系统,直接让 Redis 顶上了「数据库」的位置。
先搞清楚 Redis 是什么:
- NOSQL:没有数据表,不是关系型,不用写 SQL,也不用 ORM 驱动
- key:value :内存数据库,像 localStorage 一样直接
key:value开搞 - 跑在内存:默认端口 6379,读写速度极快
对于「个人笔记」这种量级不大、结构简单、又不强依赖关系查询的场景,Redis 完全够用,还省去了建表、写 SQL、配连接池的一大堆心智负担。
二、hash 类型:天然适合存「对象」
Redis 厉害的地方在于,它对不同类型的数据有优化的存储方式 。除了最基础的字符串 get/set,还有 hash 类型 hget/hset,正好用来存笔记对象。
一条笔记长这样:
JSON
json
{
"title": "sunt aut",
"content": "quia et suscipit suscipit recusandae",
"updateTime": "2023-12-13T09:19:48.837Z"
}
结构是「一个 id 对应一个对象」,用 Redis hash 存再合适不过:
key=notes(所有笔记的集合)field= 笔记的 id(如1702459181837)value= 序列化后的笔记 JSON 字符串
对应关系一目了然:
| 概念 | Redis hash |
|---|---|
| 整个笔记集合 | notes 这个 key |
| 单条笔记 id | field |
| 笔记内容 | value(JSON 字符串) |
三、数据服务层:lib 目录
Next.js 约定,数据业务逻辑统一放在 lib 目录 。这里我们写了一个 lib/redis.js:
js
javascript
import Redis from 'ioredis';
const redis = new Redis(); // 默认连本地 6379
// 初始数据(首次运行时 seed 进去)
const initialData = {
"1702459181837": '{"title":"sunt aut",...}',
"1702459182837": '{"title":"qui est",...}',
"1702459188837": '{"title":"ea molestias",...}'
}
export async function getAllNotes() {
// hash 数据类型:一次性取出所有笔记
const data = await redis.hgetall('notes');
// 首次为空,写入 seed 数据
if (Object.keys(data).length == 0) {
await redis.hset("notes", initialData);
}
return await redis.hgetall("notes");
}
几个细节:
ioredis是 Node 的 Redis 客户端,相当于「驱动」,帮我们跟 Redis 通信hgetall('notes')直接拿到整个 hash,返回一个{ field: value }的对象- 空数据时自动 seed,保证首次运行就有内容可看
getAllNotes 这个函数,被 Sidebar 组件在服务端 await 调用(还记得第一篇的 RSC 吗),数据就从 Redis 一路流到了页面。
四、Redis + MySQL:经典缓存套路
虽然本项目「只用 Redis」,但 Redis 更常见的定位是站在 MySQL 前面做缓存,解决数据库读写的 I/O 瓶颈。
想象掘金首页的文章列表:
- 文章列表几分钟内基本不变
- 如果每个用户访问都去查 MySQL,数据库压力巨大
- 而 Redis 是内存操作,速度快几个数量级
于是有了经典流程:
vbnet
第一个用户访问
→ 查 MySQL 拿到 posts 列表
→ 序列化成 key:value 存进 Redis
→ 返回数据
后续用户访问
→ 直接读 Redis
→ 命中缓存,返回数据(不再碰 MySQL)
这就是「缓存穿透前先缓存」的思路------热点数据放 Redis,冷数据/持久化靠 MySQL。理解了这套逻辑,也就理解了 Redis 在企业级架构里的真正价值。
五、这个设计还能怎么扩展
当前 lib/redis.js 只实现了「查全部」,但基于 hash 类型,CRUD 顺手就能补齐:
js
javascript
// 新增/更新一条
await redis.hset('notes', id, JSON.stringify(note));
// 删除一条
await redis.hdel('notes', id);
// 查单条
const note = await redis.hget('notes', id);
配合第一篇的路由设计:
- 新增 →
POST /note/edit→hset - 编辑 →
POST /note/edit/[id]→hset - 删除 →
hdel - 详情 →
GET /note/[id]→hget
搜索功能则可以在服务端 hgetall 后,对标题/内容做关键字过滤,逻辑简单直接。
总结
这篇文章我们看清了数据层的取舍:
- NOSQL 的定位:结构简单、量级可控的场景,Redis 完全能独当一面
- hash 类型:天生适合「id → 对象」这种存储结构
- 数据服务层 :集中在
lib,被服务端组件直接调用 - 缓存思想:Redis + MySQL 是处理高并发读的黄金搭档
技术选型从来不是「越重越好」,而是「刚好够用」。一个个人笔记系统,用 Redis 既轻量又优雅,还能顺手把缓存思想吃透------这就是小而美项目的价值。