大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Redis
这一课你将能做到
- 理解 Redis 五种数据类型及使用场景
- 用 Redis 做缓存,减少数据库压力
- 用 Redis 实现 Session、计数器、排行榜
为什么要学
数据库查询再快,也要读硬盘、走网络。Redis 把热数据放内存,查询速度比数据库快 100 倍。登录态、验证码、热点文章、排行榜,这些高频访问的数据都适合放 Redis。
人话:Redis 是「前台快递柜」,常用包裹放这里,不用每次都跑去大仓库(数据库)。
先建立图像
传统架构:
浏览器 → 后端 → 数据库(慢,10-100ms)
加缓存:
浏览器 → 后端 → Redis(快,1ms)
↓(缓存未命中)
数据库
跟着做
步骤 1:安装与启动
# macOS
brew install redis
brew services start redis
# Ubuntu
sudo apt install redis-server
sudo systemctl start redis
# 测试连接
redis-cli ping
# 返回 PONG
步骤 2:五种数据类型
# 1. String(字符串):缓存对象、计数器
SET user:1 '{"name":"张三","age":25}'
GET user:1
INCR article:100:views # 文章阅读量 +1
SETEX code:13800138000 300 "123456" # 验证码,5分钟过期
# 2. Hash(哈希):存对象字段
HSET user:1 name "张三" age 25
HGET user:1 name
HGETALL user:1
# 3. List(列表):消息队列、最新列表
LPUSH messages "消息1"
LPUSH messages "消息2"
LRANGE messages 0 9 # 取前10条
# 4. Set(集合):标签、去重
SADD article:100:tags "java" "redis" "后端"
SMEMBERS article:100:tags
SISMEMBER article:100:tags "java" # 判断是否存在
# 5. Sorted Set(有序集合):排行榜
ZADD leaderboard 95 "user:1"
ZADD leaderboard 88 "user:2"
ZADD leaderboard 92 "user:3"
ZREVRANGE leaderboard 0 9 WITHSCORES # 前10名
步骤 3:Node.js 连接 Redis
npm install redis
const redis = require('redis');
const client = redis.createClient({
host: 'localhost',
port: 6379
});
client.on('error', err => console.error('Redis 错误:', err));
await client.connect();
// String 操作
await client.set('user:1', JSON.stringify({ name: '张三' }));
const user = await client.get('user:1');
console.log(JSON.parse(user));
// 设置过期时间(秒)
await client.setEx('code:13800138000', 300, '123456');
// Hash 操作
await client.hSet('user:1', 'name', '张三');
await client.hSet('user:1', 'age', '25');
const userData = await client.hGetAll('user:1');
// 计数器
const views = await client.incr('article:100:views');
步骤 4:缓存模式(Cache Aside)
const pool = require('./db'); // PostgreSQL 连接池
const client = require('./redis'); // Redis 客户端
async function getCourse(id) {
const cacheKey = `course:${id}`;
// 1. 先查缓存
const cached = await client.get(cacheKey);
if (cached) {
console.log('缓存命中');
return JSON.parse(cached);
}
// 2. 缓存未命中,查数据库
console.log('缓存未命中,查数据库');
const result = await pool.query('SELECT * FROM course WHERE id = $1', [id]);
const course = result.rows[0];
if (course) {
// 3. 写入缓存,30 分钟过期
await client.setEx(cacheKey, 1800, JSON.stringify(course));
}
return course;
}
// 更新数据时,删除缓存(而非更新缓存)
async function updateCourse(id, data) {
await pool.query('UPDATE course SET title = $1 WHERE id = $2', [data.title, id]);
await client.del(`course:${id}`); // 删除缓存,下次查询时重建
}
步骤 5:实战——短信验证码
// 发送验证码
app.post('/api/sms-code', async (req, res) => {
const { phone } = req.body;
const code = Math.floor(100000 + Math.random() * 900000).toString();
// 存 Redis,5 分钟过期
await client.setEx(`sms:code:${phone}`, 300, code);
// 限制频率:1 分钟内只能发一次
await client.setEx(`sms:limit:${phone}`, 60, '1');
// TODO: 调用短信服务商 API
console.log(`发送验证码 ${code} 到 ${phone}`);
res.json({ code: 0, msg: '验证码已发送' });
});
// 校验验证码
app.post('/api/verify-code', async (req, res) => {
const { phone, code } = req.body;
const saved = await client.get(`sms:code:${phone}`);
if (!saved) {
return res.json({ code: 1, msg: '验证码已过期' });
}
if (saved !== code) {
return res.json({ code: 1, msg: '验证码错误' });
}
// 验证成功,删除验证码(一次性)
await client.del(`sms:code:${phone}`);
res.json({ code: 0, msg: '验证通过' });
});
步骤 6:实战——文章排行榜
// 阅读 +1
app.post('/api/articles/:id/read', async (req, res) => {
const { id } = req.params;
await client.zIncrBy('article:rank', 1, `article:${id}`);
res.json({ code: 0 });
});
// 获取前 10 热门文章
app.get('/api/articles/hot', async (req, res) => {
const top = await client.zRangeWithScores('article:rank', 0, 9, { REV: true });
// top: [{ value: 'article:5', score: 100 }, ...]
const ids = top.map(item => item.value.replace('article:', ''));
const articles = await pool.query(
'SELECT * FROM article WHERE id = ANY($1)', [ids]
);
res.json({ code: 0, data: articles.rows });
});
讲清楚要点
- Redis 是内存数据库:断电数据丢失(除非开持久化),所以不能当主数据库。
- 五种数据类型对应五种场景:String 缓存、Hash 对象、List 队列、Set 去重、ZSet 排行榜。
- 过期时间很重要:缓存都要设 TTL,防止内存撑爆。
- Cache Aside 模式:读先查缓存,写删缓存。是事实标准。
- 缓存穿透/击穿/雪崩:穿透(查不存在的数据)用布隆过滤器;击穿(热点 key 过期)用互斥锁;雪崩(大量 key 同时过期)用随机 TTL。
你可能会懵的地方
-
Redis 和 Memcached 选哪个? Redis 数据类型丰富、支持持久化;Memcached 只支持 String、纯内存。现代项目都用 Redis。
-
缓存更新用「删」还是「改」? 用「删」。改缓存可能和数据库不一致,删缓存下次查询时重建,更简单可靠。
-
Redis 持久化方式? RDB(快照,定期保存)、AOF(追加日志,每条命令记录)。生产环境两个都开。
-
如何保证缓存和数据库一致? 强一致很难,一般用「最终一致性」:更新数据库 → 删除缓存 → 下次查询重建。延迟双删可进一步优化。
-
Redis 集群方案? 主从复制(读写分离)、哨兵(自动故障转移)、Cluster(分片,大数据量)。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 缓存不设过期时间 | 内存撑爆 | 所有缓存 key 都设 TTL |
缓存 key 用 user:1:profile:basic:info | 太长,浪费内存 | 简短有意义:user:1:info |
| 更新数据时同时更新缓存 | 数据不一致风险 | 更新数据库,删除缓存 |
| 用 Redis 存重要业务数据 | 断电丢失 | 重要数据存数据库,Redis 只做缓存 |
| 缓存空结果不处理 | 缓存穿透 | 空结果也缓存,TTL 设短(如 60 秒) |
卡点急救
- 连接失败:检查 Redis 是否启动(
redis-cli ping)、防火墙、密码配置。 - 内存满:用
CONFIG GET maxmemory查看限制,设置maxmemory-policy allkeys-lru自动淘汰。 - 慢查询:用
SLOWLOG GET 10查看慢命令,优化大 key 或复杂操作。
面试常问
-
Redis 为什么快? 纯内存操作、单线程无锁竞争、IO 多路复用、高效数据结构(SDS、跳表)。
-
单线程为什么还这么快? 瓶颈在网络 IO 而非 CPU;单线程避免了锁竞争和上下文切换。Redis 6 引入多线程处理网络 IO,命令执行仍单线程。
-
如何实现分布式锁?
SET lock:resource random_value NX EX 30,释放锁用 Lua 脚本校验 value 再删除。RedLock 算法用于多节点。 -
Redis 数据淘汰策略? noeviction(默认,报错)、allkeys-lru(淘汰最少使用)、volatile-lru(淘汰设置 TTL 中最少使用)、allkeys-random 等。
自测三问
- Redis 五种数据类型分别适合什么场景?
- Cache Aside 模式的读写流程?
- 什么是缓存穿透?如何防?
小结一句话
Redis 是内存数据库,用五种数据类型做缓存、计数器、排行榜;Cache Aside 模式保证数据一致性。
延伸
- Redis 官方文档
- 下一课:RESTful API 设计规范。