Redis 缓存入门
本教程共 76 篇 · 第 54 篇 · 更新于 2026-07-25 · 约 4 分钟阅读
54. Redis 缓存入门
本节目标:Redis 常用数据结构,用它做缓存和会话存储。
数据库再快,也扛不住每秒几万次的重复查询。缓存的作用就是把热点数据放到更快的介质里,减轻数据库压力。Redis 是内存型键值数据库,读写性能在微秒级,是 Node.js 应用最常用的缓存方案。
安装与连接
Docker 起 Redis:
docker run -d -p 6379:6379 redis:7-alpine
Node.js 客户端用 ioredis:
npm install ioredis
import Redis from 'ioredis';
const redis = new Redis({
host: 'localhost',
port: 6379,
retryStrategy: (times) => Math.min(times * 50, 2000)
});
redis.on('error', (err) => console.error('Redis error', err));
retryStrategy 让连接断开后自动重连,生产环境必备。
基础数据结构
Redis 不只是「键值对」,值可以是多种结构:
String(字符串)
最常用,存 Token、计数器、序列化后的 JSON:
await redis.set('user:1:name', 'Alice');
await redis.set('user:1:profile', JSON.stringify({ age: 28 }), 'EX', 3600);
const name = await redis.get('user:1:name');
const profile = JSON.parse(await redis.get('user:1:profile'));
EX 3600 表示 1 小时后过期。缓存一定要设过期时间,否则内存迟早被打满。
Hash(哈希)
存对象属性,适合替代那种「把对象 JSON 化后整个塞进去」的做法:
await redis.hset('user:1', 'name', 'Alice', 'age', '28');
const user = await redis.hgetall('user:1');
// { name: 'Alice', age: '28' }
List(列表)
简单的消息队列、时间线:
await redis.lpush('news:feed', '文章A', '文章B');
const latest = await redis.lrange('news:feed', 0, 9); // 取前 10 条
Set(集合)
去重、判断存在性:
await redis.sadd('tags:nodejs', 'javascript', 'backend', 'server');
const isMember = await redis.sismember('tags:nodejs', 'backend'); // 1
Sorted Set(有序集合)
排行榜、优先级队列:
await redis.zadd('rank:score', 100, 'player1', 95, 'player2');
const top = await redis.zrevrange('rank:score', 0, 4, 'WITHSCORES');
缓存模式
Cache-Aside(旁路缓存)
最常用的模式:应用先查缓存,命中就返回;没命中查数据库,写入缓存,再返回。
async function getUser(id) {
const cacheKey = `user:${id}`;
let data = await redis.get(cacheKey);
if (data) {
return JSON.parse(data);
}
data = await db.query('SELECT * FROM users WHERE id = ?', [id]);
if (data) {
await redis.set(cacheKey, JSON.stringify(data), 'EX', 300);
}
return data;
}
缓存更新策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 先更新数据库,再删缓存 | 简单可靠,推荐 | 大多数写场景 |
| 先更新数据库,再更新缓存 | 可能写脏数据 | 读多写少、允许短暂不一致 |
| 写直达(Write Through) | 同步写数据库和缓存 | 强一致性要求 |
推荐「先更新数据库,再删缓存」的原因是:删除操作是幂等的,即使删失败,下次读也会从数据库回填,不会长期不一致。
缓存穿透、击穿、雪崩
这三个词面试常考,实际开发也容易踩:
缓存穿透:查询一个数据库和缓存里都没有的数据,每次都打到数据库。
解决:布隆过滤器,或者缓存空值(set(key, 'null', 'EX', 60))。
缓存击穿:热点 key 突然过期,大量请求同时打到数据库。
解决:热点 key 永不过期(逻辑过期),或者加互斥锁,只允许一个线程去重建缓存。
缓存雪崩:大量 key 同时过期,数据库压力陡增。
解决:过期时间加随机偏移,比如 EX 300 + Math.random() * 60。
连接池与集群
单机 Redis 性能足够大部分场景。如果需要更高可用,用 Redis Cluster:
const redis = new Redis.Cluster([
{ host: '192.168.1.1', port: 6379 },
{ host: '192.168.1.2', port: 6379 }
]);