Node.js Redis 缓存实战:缓存读写与一致性处理
Redis 缓存让 Node.js 应用响应提速一个量级。本文讲解连接配置、API 与页面缓存实战、缓存键设计、三大缓存策略与缓存一致性方案。
Redis 缓存的核心模式是 Cache-Aside:先查缓存,未命中查数据库并回填,设 TTL 兜底过期。三个关键决策——缓存键设计(含版本号)、TTL 策略(基础值+随机抖动防雪崩)、更新时的失效策略(写后删缓存而非更新缓存)。
连接 Redis
npm install redis
const { createClient } = require('redis');
const redis = createClient({ url: process.env.REDIS_URL });
redis.on('error', (err) => console.error('Redis 错误:', err));
await redis.connect();
场景一:缓存 API 响应
async function getUser(id) {
const cacheKey = `user:${id}`;
// 1. 先查缓存
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
// 2. 未命中查数据库
const user = await db.users.findById(id);
if (!user) return null;
// 3. 回填缓存,TTL 300 秒
await redis.setEx(cacheKey, 300, JSON.stringify(user));
return user;
}
场景二:缓存整页响应
app.get('/api/popular-posts', async (req, res) => {
const cached = await redis.get('posts:popular');
if (cached) {
res.setHeader('X-Cache', 'HIT');
return res.json(JSON.parse(cached));
}
const posts = await db.getPopularPosts();
await redis.setEx('posts:popular', 60, JSON.stringify(posts));
res.setHeader('X-Cache', 'MISS');
res.json(posts);
});
X-Cache 响应头是排障利器——联调时一眼看出缓存是否生效。
缓存键设计规范
- 命名空间:
业务:实体:标识(shop:product:123),可冒号分层; - 参数进键:列表缓存要含分页/筛选参数(
posts:page:2:sort:hot); - 版本号:
v2:user:123——数据结构升级时整体换代,旧键自然过期。
三大缓存策略
| 策略 | 模式 | 适用 |
|---|---|---|
| Cache-Aside | 读:先缓存后库;写:更新库+删缓存 | 绝大多数场景(本文模式) |
| Write-Through | 写缓存与库同步完成 | 一致性要求高的读多写少 |
| Write-Behind | 先写缓存异步刷库 | 写密集、可容忍延迟 |
缓存一致性:实战要点
写后删缓存,而不是更新缓存:
async function updateUser(id, data) {
await db.users.update(id, data);
await redis.del(`user:${id}`); // 删除而非更新——下次读时回填最新
}
更新缓存有并发乱序问题(两个写请求交错导致旧值覆盖新值);删除模式让"下次读"自然拿到最新值。TTL 是最后防线——即使失效逻辑漏了,数据也不会永远错。
防雪崩:大量键同时过期会打穿数据库——TTL 加随机抖动(300 + Math.random()*60)。
用命中率和回源延迟验证缓存收益
缓存加上后,接口变快还是偶尔更慢,需要同时看 Redis 命中与淘汰、应用回源次数和数据库调用耗时。观测云 Redis 采集器提供实例层指标;业务键空间的命中率通常需要应用单独计数。为命中率设阈值前先建立业务基线,避免把固定百分比当作所有服务的合格线;高基数键和值也不应直接作为监控标签。
接入文档:Redis 采集 · Node.js APM 接入 · 监控器与告警策略。
常见问题(FAQ)
Q:缓存穿透怎么办?(查询不存在的数据)
缓存空值(null 也缓存 30 秒)挡住恶意刷库;大流量场景用布隆过滤器前置拦截。
Q:多实例应用的本地缓存和 Redis 怎么选?
多实例部署必须 Redis(本地缓存各实例不一致);单实例小应用内存缓存(lru-cache)更轻。集群化的正确姿势见本系列《Node.js 集群扩容实战》。
Q:缓存和数据库事务一致性怎么保证?
先写数据库再删缓存仍存在并发回填旧值和删除失败窗口。TTL 只提供过期上限;需要重试、版本校验或失效事件等机制,严格一致性不能仅靠一把分布式锁保证。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。