缓存策略
概览
实施有效的缓存策略以提高应用程序性能,减少延迟,并减轻后端系统的负载。
何时使用
- 减少数据库查询负载
- 提高API响应时间
- 处理高流量负载
- 缓存昂贵的计算
- 存储会话数据
- CDN集成静态资产
- 实施分布式缓存
- 速率限制和节流
缓存层
┌─────────────────────────────────────────┐
│ 客户端浏览器缓存 │
├─────────────────────────────────────────┤
│ CDN缓存 │
├─────────────────────────────────────────┤
│ 应用程序内存缓存 │
├─────────────────────────────────────────┤
│ 分布式缓存(Redis) │
├─────────────────────────────────────────┤
│ 数据库 │
└─────────────────────────────────────────┘
实施示例
1. Redis缓存实现(Node.js)
import Redis from 'ioredis';
interface CacheOptions {
ttl?: number; // 存活时间,以秒为单位
prefix?: string;
}
class CacheService {
private redis: Redis;
private defaultTTL = 3600; // 1小时
constructor(redisUrl: string) {
this.redis = new Redis(redisUrl, {
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
},
maxRetriesPerRequest: 3
});
this.redis.on('connect', () => {
console.log('Redis connected');
});
this.redis.on('error', (error) => {
console.error('Redis error:', error);
});
}
/**
* 获取缓存值
*/
async get<T>(key: string): Promise<T | null> {
try {
const value = await this.redis.get(key);
if (!value) return null;
return JSON.parse(value) as T;
} catch (error) {
console.error(`Cache get error for key ${key}:`, error);
return null;
}
}
/**
* 设置缓存值
*/
async set(
key: string,
value: any,
options: CacheOptions = {}
): Promise<boolean> {
try {
const ttl = options.ttl || this.defaultTTL;
const serialized = JSON.stringify(value);
if (ttl > 0) {
await this.redis.setex(key, ttl, serialized);
} else {
await this.redis.set(key, serialized);
}
return true;
} catch (error) {
console.error(`Cache set error for key ${key}:`, error);
return false;
}
}
/**
* 删除缓存值
*/
async delete(key: string): Promise<boolean> {
try {
await this.redis.del(key);
return true;
} catch (error) {
console.error(`Cache delete error for key ${key}:`, error);
return false;
}
}
/**
* 按模式删除多个键
*/
async deletePattern(pattern: string): Promise<number> {
try {
const keys = await this.redis.keys(pattern);
if (keys.length === 0) return 0;
await this.redis.del(...keys);
return keys.length;
} catch (error) {
console.error(`Cache delete pattern error for ${pattern}:`, error);
return 0;
}
}
/**
* 获取或设置模式 - 从缓存中获取或计算并缓存
*/
async getOrSet<T>(
key: string,
fetchFn: () => Promise<T>,
options: CacheOptions = {}
): Promise<T> {
// 尝试从缓存中获取
const cached = await this.get<T>(key);
if (cached !== null) {
return cached;
}
// 获取并缓存
const value = await fetchFn();
await this.set(key, value, options);
return value;
}
/**
* 实现带有失效重验证的缓存旁路模式
*/
async getStaleWhileRevalidate<T>(
key: string,
fetchFn: () => Promise<T>,
options: {
ttl: number;
staleTime: number;
}
): Promise<T> {
const cacheKey = `cache:${key}`;
const timestampKey = `cache:${key}:timestamp`;
const [cached, timestamp] = await Promise.all([
this.get<T>(cacheKey),
this.redis.get(timestampKey)
]);
const now = Date.now();
const age = timestamp ? now - parseInt(timestamp) : Infinity;
// 如果新鲜则返回缓存
if (cached !== null && age < options.ttl * 1000) {
return cached;
}
// 如果陈旧则在后台重新验证
if (cached !== null && age < options.staleTime * 1000) {
// 后台重新验证
fetchFn()
.then(async (fresh) => {
await this.set(cacheKey, fresh, { ttl: options.ttl });
await this.redis.set(timestampKey, now.toString());
})
.catch(console.error);
return cached;
}
// 获取新鲜数据
const fresh = await fetchFn();
await Promise.all([
this.set(cacheKey, fresh, { ttl: options.ttl }),
this.redis.set(timestampKey, now.toString())
]);
return fresh;
}
/**
* 带有TTL的计数器增加
*/
async increment(key: string, ttl?: number): Promise<number> {
const count = await this.redis.incr(key);
if (count === 1 && ttl) {
await this.redis.expire(key, ttl);
}
return count;
}
/**
* 检查键是否存在
*/
async exists(key: string): Promise<boolean> {
const result = await this.redis.exists(key);
return result === 1;
}
/**
* 获取剩余TTL
*/
async ttl(key: string): Promise<number> {
return await this.redis.ttl(key);
}
/**
* 关闭连接
*/
async disconnect(): Promise<void> {
await this.redis.quit();
}
}
// 使用
const cache = new CacheService('redis://localhost:6379');
// 简单的get/set
await cache.set('user:123', { name: 'John', age: 30 }, { ttl: 3600 });
const user = await cache.get('user:123');
// 获取或设置模式
const posts = await cache.getOrSet(
'posts:recent',
async () => {
return await database.query('SELECT * FROM posts ORDER BY created_at DESC LIMIT 10');
},
{ ttl: 300 }
);
// 失效重验证
const data = await cache.getStaleWhileRevalidate(
'expensive-query',
async () => await runExpensiveQuery(),
{ ttl: 300, staleTime: 600 }
);
2. 缓存装饰器(Python)
import functools
import json
import hashlib
from typing import Any, Callable, Optional
from redis import Redis
import time
class CacheDecorator:
def __init__(self, redis_client: Redis, ttl: int = 3600):
self.redis = redis_client
self.ttl = ttl
def cache_key(self, func: Callable, *args, **kwargs) -> str:
"""根据函数名称和参数生成缓存键。"""
# 从函数和参数创建确定性键
key_parts = [
func.__module__,
func.__name__,
str(args),
str(sorted(kwargs.items()))
]
key_string = ':'.join(key_parts)
key_hash = hashlib.md5(key_string.encode()).hexdigest()
return f"cache:{func.__name__}:{key_hash}"
def __call__(self, func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 生成缓存键
cache_key = self.cache_key(func, *args, **kwargs)
# 尝试从缓存中获取
cached = self.redis.get(cache_key)
if cached:
print(f"Cache HIT: {cache_key}")
return json.loads(cached)
# 缓存未命中 - 执行函数
print(f"Cache MISS: {cache_key}")
result = func(*args, **kwargs)
# 存储在缓存中
self.redis.setex(
cache_key,
self.ttl,
json.dumps(result)
)
return result
# 添加缓存失效方法
def invalidate(*args, **kwargs):
cache_key = self.cache_key(func, *args, **kwargs)
self.redis.delete(cache_key)
wrapper.invalidate = invalidate
return wrapper
# 使用
redis = Redis(host='localhost', port=6379, db=0)
cache = CacheDecorator(redis, ttl=300)
@cache
def get_user_profile(user_id: int) -> dict:
"""从数据库获取用户资料。"""
print(f"Fetching user {user_id} from database...")
# 模拟数据库查询
time.sleep(1)
return {
'id': user_id,
'name': 'John Doe',
'email': 'john@example.com'
}
# 第一次调用 - 缓存未命中
profile = get_user_profile(123) # 需要1秒钟
# 第二次调用 - 缓存命中
profile = get_user_profile(123) # 立即
# 使缓存失效
get_user_profile.invalidate(123)
3. 多级缓存
interface CacheLevel {
get(key: string): Promise<any>;
set(key: string, value: any, ttl?: number): Promise<void>;
delete(key: string): Promise<void>;
}
class MemoryCache implements CacheLevel {
private cache = new Map<string, { value: any; expiry: number }>();
async get(key: string): Promise<any> {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expiry) {
this.cache.delete(key);
return null;
}
return item.value;
}
async set(key: string, value: any, ttl: number = 60): Promise<void> {
this.cache.set(key, {
value,
expiry: Date.now() + ttl * 1000
});
}
async delete(key: string): Promise<void> {
this.cache.delete(key);
}
clear(): void {
this.cache.clear();
}
}
class RedisCache implements CacheLevel {
constructor(private redis: Redis) {}
async get(key: string): Promise<any> {
const value = await this.redis.get(key);
return value ? JSON.parse(value) : null;
}
async set(key: string, value: any, ttl: number = 3600): Promise<void> {
await this.redis.setex(key, ttl, JSON.stringify(value));
}
async delete(key: string): Promise<void> {
await this.redis.del(key);
}
}
class MultiLevelCache {
private levels: CacheLevel[];
constructor(levels: CacheLevel[]) {
this.levels = levels; // 从最快到最慢排序
}
async get<T>(key: string): Promise<T | null> {
for (let i = 0; i < this.levels.length; i++) {
const value = await this.levels[i].get(key);
if (value !== null) {
// 回填更快的缓存
for (let j = 0; j < i; j++) {
await this.levels[j].set(key, value);
}
return value as T;
}
}
return null;
}
async set(key: string, value: any, ttl?: number): Promise<void> {
// 在所有缓存层中设置
await Promise.all(
this.levels.map(level => level.set(key, value, ttl))
);
}
async delete(key: string): Promise<void> {
await Promise.all(
this.levels.map(level => level.delete(key))
);
}
}
// 使用
const cache = new MultiLevelCache([
new MemoryCache(),
new RedisCache(redis)
]);
// 从最快的可用缓存中获取
const data = await cache.get('user:123');
// 在所有缓存中设置
await cache.set('user:123', userData, 3600);
4. 缓存失效策略
class CacheInvalidation {
constructor(private cache: CacheService) {}
/**
* 基于时间的失效(TTL)
*/
async setWithTTL(key: string, value: any, seconds: number): Promise<void> {
await this.cache.set(key, value, { ttl: seconds });
}
/**
* 基于标签的失效
*/
async setWithTags(
key: string,
value: any,
tags: string[]
): Promise<void> {
// 存储值
await this.cache.set(key, value);
// 存储标签关联
for (const tag of tags) {
await this.cache.redis.sadd(`tag:${tag}`, key);
}
}
async invalidateByTag(tag: string): Promise<number> {
// 获取所有具有此标签的键
const keys = await this.cache.redis.smembers(`tag:${tag}`);
if (keys.length === 0) return 0;
// 删除所有键
await Promise.all(
keys.map(key => this.cache.delete(key))
);
// 删除标签集合
await this.cache.redis.del(`tag:${tag}`);
return keys.length;
}
/**
* 基于事件的失效
*/
async invalidateOnEvent(
entity: string,
id: string,
event: 'create' | 'update' | 'delete'
): Promise<void> {
const patterns = [
`${entity}:${id}`,
`${entity}:${id}:*`,
`${entity}:list:*`,
`${entity}:count`
];
for (const pattern of patterns) {
await this.cache.deletePattern(pattern);
}
}
/**
* 基于版本的失效
*/
async setVersioned(
key: string,
value: any,
version: number
): Promise<void> {
const versionedKey = `${key}:v${version}`;
await this.cache.set(versionedKey, value);
await this.cache.set(`${key}:version`, version);
}
async getVersioned(key: string): Promise<any> {
const version = await this.cache.get<number>(`${key}:version`);
if (!version) return null;
return await this.cache.get(`${key}:v${version}`);
}
}
5. HTTP缓存头
import express from 'express';
const app = express();
// Cache-Control中间件
function cacheControl(maxAge: number, options: {
private?: boolean;
noStore?: boolean;
noCache?: boolean;
mustRevalidate?: boolean;
staleWhileRevalidate?: number;
} = {}) {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
const directives: string[] = [];
if (options.noStore) {
directives.push('no-store');
} else if (options.noCache) {
directives.push('no-cache');
} else {
directives.push(options.private ? 'private' : 'public');
directives.push(`max-age=${maxAge}`);
if (options.staleWhileRevalidate) {
directives.push(`stale-while-revalidate=${options.staleWhileRevalidate}`);
}
}
if (options.mustRevalidate) {
directives.push('must-revalidate');
}
res.setHeader('Cache-Control', directives.join(', '));
next();
};
}
// 静态资产 - 长缓存
app.use('/static', cacheControl(31536000), express.static('public'));
// API - 短缓存与重验证
app.get('/api/data',
cacheControl(60, { staleWhileRevalidate: 300 }),
(req, res) => {
res.json({ data: 'cached for 60s' });
}
);
// 动态内容 - 无缓存
app.get('/api/user/profile',
cacheControl(0, { private: true, noCache: true }),
(req, res) => {
res.json({ user: 'always fresh' });
}
);
// ETag支持
app.get('/api/resource/:id', async (req, res) => {
const resource = await getResource(req.params.id);
const etag = generateETag(resource);
res.setHeader('ETag', etag);
// 检查客户端是否有当前版本
if (req.headers['if-none-match'] === etag) {
return res.status(304).end();
}
res.json(resource);
});
function generateETag(data: any): string {
return require('crypto')
.createHash('md5')
.update(JSON.stringify(data))
.digest('hex');
}
最佳实践
✅ 执行
- 设置适当的TTL值
- 为关键数据实施缓存预热
- 对读取使用缓存旁路模式
- 监控缓存命中率
- 在缓存失败时实现优雅降级
- 对大的缓存值使用压缩
- 适当命名空间缓存键
- 实施缓存踩踏预防
- 对分布式缓存使用一致性哈希
- 监控缓存内存使用情况
❌ 不执行
- 无差别地缓存一切
- 使用缓存作为数据库设计不佳的解决方案
- 未经加密存储敏感数据
- 忘记处理缓存未命中
- 为经常变化的数据设置过长的TTL
- 忽略缓存失效策略
- 不监控缓存
- 无考虑地存储大对象
缓存策略
| 策略 | 描述 | 用例 |
|---|---|---|
| 旁路缓存 | 应用程序检查缓存,未命中时从数据库加载 | 通用目的 |
| 写入旁路 | 同时写入缓存和数据库 | 需要强一致性 |
| 写入后 | 写入缓存,异步写入数据库 | 高写入吞吐量 |
| 提前刷新 | 在到期前主动刷新 | 可预测的访问模式 |
| 读取旁路 | 缓存自动从数据库加载 | 简化代码 |