缓存策略概述
缓存是提升应用性能的关键技术。Claude Code 可以帮助你实现多层次缓存策略,从内存缓存到分布式 Redis 缓存。
缓存适用场景
缓存适用于频繁读取但不频繁修改的数据,如配置信息、用户会话、商品详情等。
内存缓存
Node.js 内存缓存
bash
帮我创建一个简单的内存缓存模块,包含:
- 设置缓存(支持过期时间)
- 获取缓存
- 删除缓存
- 清除所有缓存javascript
// lib/cache/memory.js
class MemoryCache {
constructor() {
this.cache = new Map();
this.timers = new Map();
}
set(key, value, ttl = 0) {
// 清除已有的定时器
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key));
}
this.cache.set(key, {
value,
timestamp: Date.now(),
});
// 设置过期时间
if (ttl > 0) {
this.timers.set(
key,
setTimeout(() => {
this.delete(key);
}, ttl * 1000)
);
}
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
return item.value;
}
delete(key) {
this.cache.delete(key);
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key));
this.timers.delete(key);
}
}
clear() {
this.cache.clear();
this.timers.forEach((timer) => clearTimeout(timer));
this.timers.clear();
}
// 获取缓存统计
getStats() {
return {
size: this.cache.size,
keys: Array.from(this.cache.keys()),
};
}
}
export const memoryCache = new MemoryCache();
export default memoryCache;LRU 缓存实现
javascript
// lib/cache/lru.js
class LRUCache {
constructor(maxSize = 100) {
this.maxSize = maxSize;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) {
return null;
}
// 将键移到末尾(最近使用)
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
set(key, value) {
// 如果已存在,删除旧位置
if (this.cache.has(key)) {
this.cache.delete(key);
}
// 如果缓存已满,删除最旧的项
else if (this.cache.size >= this.maxSize) {
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
this.cache.set(key, value);
}
has(key) {
return this.cache.has(key);
}
delete(key) {
return this.cache.delete(key);
}
clear() {
this.cache.clear();
}
}
export const lruCache = new LRUCache(100);
export default lruCache;Redis 缓存
Redis 基础配置
bash
帮我创建一个 Redis 缓存模块,包含:
- 连接管理
- 基础操作(get/set/del)
- 过期时间设置
- 模式匹配删除javascript
// lib/cache/redis.js
import Redis from "ioredis";
class RedisCache {
constructor() {
this.client = null;
}
async connect() {
if (this.client) return this.client;
this.client = new Redis({
host: process.env.REDIS_HOST || "localhost",
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD,
db: process.env.REDIS_DB || 0,
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3,
});
this.client.on("error", (err) => {
console.error("Redis connection error:", err);
});
this.client.on("connect", () => {
console.log("Redis connected successfully");
});
return this.client;
}
async get(key) {
const client = await this.connect();
const value = await client.get(key);
return value ? JSON.parse(value) : null;
}
async set(key, value, ttlSeconds = 0) {
const client = await this.connect();
const serialized = JSON.stringify(value);
if (ttlSeconds > 0) {
await client.setex(key, ttlSeconds, serialized);
} else {
await client.set(key, serialized);
}
}
async delete(key) {
const client = await this.connect();
return client.del(key);
}
async exists(key) {
const client = await this.connect();
return (await client.exists(key)) === 1;
}
async deletePattern(pattern) {
const client = await this.connect();
const keys = await client.keys(pattern);
if (keys.length > 0) {
return client.del(...keys);
}
return 0;
}
// 哈希操作
async hget(key, field) {
const client = await this.connect();
const value = await client.hget(key, field);
return value ? JSON.parse(value) : null;
}
async hset(key, field, value) {
const client = await this.connect();
return client.hset(key, field, JSON.stringify(value));
}
async hgetall(key) {
const client = await this.connect();
const data = await client.hgetall(key);
const result = {};
for (const [k, v] of Object.entries(data)) {
result[k] = JSON.parse(v);
}
return result;
}
// 列表操作
async lpush(key, ...values) {
const client = await this.connect();
const serialized = values.map((v) => JSON.stringify(v));
return client.lpush(key, ...serialized);
}
async lrange(key, start, stop) {
const client = await this.connect();
const data = await client.lrange(key, start, stop);
return data.map((v) => JSON.parse(v));
}
// 分布式锁
async lock(key, ttlSeconds = 10) {
const client = await this.connect();
const token = Math.random().toString(36).substring(2);
const result = await client.set(key, token, "EX", ttlSeconds, "NX");
return result === "OK" ? token : null;
}
async unlock(key, token) {
const client = await this.connect();
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
return client.eval(script, 1, key, token);
}
}
export const redisCache = new RedisCache();
export default redisCache;缓存策略实现
多级缓存
bash
帮我创建一个多级缓存系统,先查内存缓存,没有再查 Redis,都没有就查数据库并回填缓存。javascript
// lib/cache/multi-level.js
import memoryCache from "./memory.js";
import redisCache from "./redis.js";
class MultiLevelCache {
constructor(options = {}) {
this.memoryTTL = options.memoryTTL || 60; // 内存缓存 60 秒
this.redisTTL = options.redisTTL || 3600; // Redis 缓存 1 小时
this.enableMemory = options.enableMemory !== false;
this.enableRedis = options.enableRedis !== false;
}
async get(key, fetchFunc) {
// 1. 尝试从内存缓存获取
if (this.enableMemory) {
const memoryValue = memoryCache.get(key);
if (memoryValue !== null) {
console.log(`[Cache] Memory hit: ${key}`);
return memoryValue;
}
}
// 2. 尝试从 Redis 获取
if (this.enableRedis) {
try {
const redisValue = await redisCache.get(key);
if (redisValue !== null) {
console.log(`[Cache] Redis hit: ${key}`);
// 回填到内存缓存
if (this.enableMemory) {
memoryCache.set(key, redisValue, this.memoryTTL);
}
return redisValue;
}
} catch (error) {
console.error("Redis get error:", error);
}
}
// 3. 从数据源获取
if (fetchFunc) {
console.log(`[Cache] Miss: ${key}, fetching from source`);
const value = await fetchFunc();
// 回填到缓存
await this.set(key, value);
return value;
}
return null;
}
async set(key, value) {
// 设置内存缓存
if (this.enableMemory) {
memoryCache.set(key, value, this.memoryTTL);
}
// 设置 Redis 缓存
if (this.enableRedis) {
try {
await redisCache.set(key, value, this.redisTTL);
} catch (error) {
console.error("Redis set error:", error);
}
}
}
async delete(key) {
if (this.enableMemory) {
memoryCache.delete(key);
}
if (this.enableRedis) {
try {
await redisCache.delete(key);
} catch (error) {
console.error("Redis delete error:", error);
}
}
}
async clear() {
if (this.enableMemory) {
memoryCache.clear();
}
if (this.enableRedis) {
try {
await redisCache.deletePattern("*");
} catch (error) {
console.error("Redis clear error:", error);
}
}
}
}
export const multiLevelCache = new MultiLevelCache();
export default multiLevelCache;缓存预热
javascript
// lib/cache/warmup.js
import multiLevelCache from "./multi-level.js";
export async function warmupCache() {
console.log("Starting cache warmup...");
const warmupTasks = [
// 热门文章
() =>
multiLevelCache.get("popular_posts", async () => {
const posts = await db.query(
"SELECT * FROM posts WHERE published = true ORDER BY view_count DESC LIMIT 20"
);
return posts;
}),
// 站点配置
() =>
multiLevelCache.get("site_config", async () => {
const config = await db.query("SELECT * FROM site_config");
return config;
}),
// 分类列表
() =>
multiLevelCache.get("categories", async () => {
const categories = await db.query(
"SELECT * FROM categories ORDER BY sort_order"
);
return categories;
}),
];
await Promise.all(warmupTasks.map((task) => task()));
console.log("Cache warmup completed");
}缓存应用场景
接口缓存
bash
帮我创建一个带缓存的 API 数据获取函数,使用多级缓存。javascript
// services/postService.js
import multiLevelCache from "../lib/cache/multi-level.js";
export async function getPostBySlug(slug) {
const cacheKey = `post:${slug}`;
return multiLevelCache.get(cacheKey, async () => {
const post = await db.query("SELECT * FROM posts WHERE slug = $1", [slug]);
return post[0] || null;
});
}
export async function getUserProfile(userId) {
const cacheKey = `user:profile:${userId}`;
return multiLevelCache.get(cacheKey, async () => {
const user = await db.query("SELECT * FROM users WHERE id = $1", [userId]);
return user[0] || null;
});
}
export async function invalidatePostCache(slug) {
await multiLevelCache.delete(`post:${slug}`);
}
export async function invalidateUserCache(userId) {
await multiLevelCache.delete(`user:profile:${userId}`);
}会话缓存
javascript
// lib/session.js
import redisCache from "./redis.js";
const SESSION_TTL = 24 * 60 * 60; // 24 小时
export async function createSession(userId) {
const sessionId = generateSessionId();
const sessionData = {
userId,
createdAt: Date.now(),
expiresAt: Date.now() + SESSION_TTL * 1000,
};
await redisCache.set(`session:${sessionId}`, sessionData, SESSION_TTL);
return sessionId;
}
export async function getSession(sessionId) {
const session = await redisCache.get(`session:${sessionId}`);
if (!session) return null;
// 检查是否过期
if (session.expiresAt < Date.now()) {
await redisCache.delete(`session:${sessionId}`);
return null;
}
return session;
}
export async function destroySession(sessionId) {
await redisCache.delete(`session:${sessionId}`);
}
function generateSessionId() {
return `${Date.now()}-${Math.random().toString(36).substring(2)}`;
}缓存失效策略
TTL 策略
javascript
// 不同数据使用不同的 TTL
const CACHE_TTL = {
// 频繁更新但读取也多
userSession: 60 * 60, // 1 小时
// 配置类数据,更新不频繁
siteConfig: 24 * 60 * 60, // 24 小时
// 内容类数据
popularPosts: 5 * 60, // 5 分钟
postDetail: 30 * 60, // 30 分钟
// 列表类数据
categoryList: 10 * 60, // 10 分钟
tagList: 60 * 60, // 1 小时
};主动失效
javascript
// 发布文章时清除相关缓存
export async function publishPost(postId) {
// 更新数据库
await db.query("UPDATE posts SET published = true WHERE id = $1", [postId]);
// 获取文章信息用于清除缓存
const post = await db.query("SELECT slug FROM posts WHERE id = $1", [postId]);
// 清除相关缓存
await multiLevelCache.delete(`post:${post.slug}`);
await multiLevelCache.delete("popular_posts"); // 清除热门文章缓存
await multiLevelCache.delete("categories"); // 清除分类缓存(如果有变化)
}最佳实践
缓存雪崩
javascript
// 使用随机 TTL 避免雪崩
function getRandomTTL(baseTTL, variance = 0.2) {
const min = baseTTL * (1 - variance);
const max = baseTTL * (1 + variance);
return Math.floor(Math.random() * (max - min) + min);
}
// 使用互斥锁避免缓存击穿
export async function getWithLock(key, fetchFunc) {
const value = await multiLevelCache.get(key, null);
if (value !== null) return value;
const lockKey = `lock:${key}`;
const lock = await redisCache.lock(lockKey, 10);
if (lock) {
try {
const value = await fetchFunc();
await multiLevelCache.set(key, value);
return value;
} finally {
await redisCache.unlock(lockKey, lock);
}
} else {
// 等待其他进程加载
await new Promise((resolve) => setTimeout(resolve, 100));
return multiLevelCache.get(key, fetchFunc);
}
}缓存监控
javascript
// lib/cache/monitor.js
export async function getCacheStats() {
const stats = {
memory: memoryCache.getStats(),
redis: await redisCache.get("stats:cache") || {},
};
return stats;
}缓存优化效果
合理的缓存策略可以提升应用性能 10-100 倍,大幅降低数据库压力。
总结
使用 Claude Code 实现缓存系统:
- 选择合适的缓存层(内存/Redis)
- 设计合理的缓存键命名规则
- 设置适当的 TTL 过期时间
- 实现多级缓存提高命中率
- 处理缓存雪崩和击穿问题
- 监控缓存命中率并持续优化