Appearance
缓存策略
缓存是提高 Web 应用性能的关键技术之一。本指南将详细介绍各种缓存策略及其实施方法。
HTTP 缓存
缓存头设置
javascript
// Express.js 中的 HTTP 缓存设置
const express = require('express');
const app = express();
// 静态资源缓存
app.use('/static', express.static('public', {
maxAge: '1y', // 1年缓存期
etag: true, // 启用 ETag
lastModified: true // 启用 Last-Modified
}));
// 动态内容缓存中间件
const cacheControl = (options = {}) => {
const defaults = {
maxAge: 600, // 10分钟默认缓存
sMaxAge: 600, // 共享缓存时间
public: true,
immutable: false
};
const opts = { ...defaults, ...options };
return (req, res, next) => {
const directives = [];
if (opts.public) directives.push('public');
if (opts.private) directives.push('private');
if (opts.noCache) directives.push('no-cache');
if (opts.noStore) directives.push('no-store');
directives.push(`max-age=${opts.maxAge}`);
if (opts.sMaxAge) directives.push(`s-maxage=${opts.sMaxAge}`);
if (opts.immutable) directives.push('immutable');
res.set('Cache-Control', directives.join(', '));
if (opts.etag) {
// 生成 ETag
const entity = JSON.stringify(res.locals.cacheData || {});
res.set('ETag', `"${generateHash(entity)}"`);
}
next();
};
};
// ETag 生成函数
function generateHash(content) {
const crypto = require('crypto');
return crypto.createHash('md5').update(content).digest('hex');
}
// 使用缓存中间件
app.get('/api/data/:id', cacheControl({ maxAge: 300, sMaxAge: 300 }), (req, res) => {
// API 响应逻辑
const data = getDataById(req.params.id);
res.json(data);
});
条件请求处理
javascript
// 条件请求中间件
const conditionalRequest = () => {
return (req, res, next) => {
const ifNoneMatch = req.headers['if-none-match'];
const ifModifiedSince = req.headers['if-modified-since'];
// 检查 ETag
if (ifNoneMatch && res.get('ETag') === ifNoneMatch) {
return res.status(304).end();
}
// 检查 Last-Modified
const lastModified = res.get('Last-Modified');
if (lastModified && ifModifiedSince) {
const lastModTime = new Date(lastModified).getTime();
const ifModTime = new Date(ifModifiedSince).getTime();
if (lastModTime <= ifModTime) {
return res.status(304).end();
}
}
next();
};
};
// 使用条件请求
app.get('/api/resource/:id', conditionalRequest(), (req, res) => {
const resource = getResource(req.params.id);
const etag = `"${generateHash(JSON.stringify(resource))}"`;
res.set({
'ETag': etag,
'Last-Modified': new Date(resource.updatedAt).toUTCString()
});
res.json(resource);
});
浏览器缓存
Service Worker 缓存
javascript
// service-worker.js
const CACHE_NAME = 'app-cache-v1';
const STATIC_CACHE = 'static-cache-v1';
const DYNAMIC_CACHE = 'dynamic-cache-v1';
const STATIC_ASSETS = [
'/',
'/index.html',
'/css/style.css',
'/js/main.js',
'/images/logo.png'
];
// 安装事件 - 缓存静态资源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(STATIC_CACHE)
.then((cache) => cache.addAll(STATIC_ASSETS))
.then(() => self.skipWaiting())
);
});
// 激活事件 - 清理旧缓存
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) {
return caches.delete(cacheName);
}
})
);
}).then(() => self.clients.claim())
);
});
// 获取事件 - 实现缓存策略
self.addEventListener('fetch', (event) => {
const { request } = event;
// 静态资源:缓存优先策略
if (isStaticAsset(request.url)) {
event.respondWith(
caches.match(request)
.then((response) => {
if (response) {
// 后台更新缓存
event.waitUntil(updateStaticCache(request));
return response;
}
return fetchFromNetworkAndUpdateCache(request);
})
);
}
// API 请求:网络优先策略,带降级
else if (isApiRequest(request.url)) {
event.respondWith(
caches.match(request)
.then((cachedResponse) => {
const networkFetch = fetch(request)
.then((networkResponse) => {
// 更新缓存
if (networkResponse.ok) {
const responseClone = networkResponse.clone();
event.waitUntil(
caches.open(DYNAMIC_CACHE)
.then((cache) => cache.put(request, responseClone))
);
}
return networkResponse;
})
.catch(() => cachedResponse); // 网络失败时返回缓存
return cachedResponse || networkFetch;
})
);
}
// 其他请求:网络优先
else {
event.respondWith(
fetch(request)
.catch(() => caches.match('/offline.html'))
);
}
});
// 辅助函数
function isStaticAsset(url) {
return /\.(css|js|png|jpg|jpeg|gif|ico|svg)$/.test(url);
}
function isApiRequest(url) {
return /\/api\//.test(url);
}
function updateStaticCache(request) {
return fetch(request)
.then((response) => {
if (response.ok) {
return caches.open(STATIC_CACHE)
.then((cache) => cache.put(request, response));
}
});
}
function fetchFromNetworkAndUpdateCache(request) {
return fetch(request)
.then((response) => {
if (response.ok) {
const responseClone = response.clone();
caches.open(STATIC_CACHE)
.then((cache) => cache.put(request, responseClone));
}
return response;
});
}
JavaScript 缓存策略
javascript
// 内存缓存实现
class InMemoryCache {
constructor(options = {}) {
this.cache = new Map();
this.maxSize = options.maxSize || 100;
this.defaultTTL = options.defaultTTL || 300000; // 5分钟
this.interval = options.interval || 60000; // 1分钟清理间隔
// 启动定期清理
this.startCleanup();
}
set(key, value, ttl = this.defaultTTL) {
if (this.cache.size >= this.maxSize) {
// 简单的 FIFO 清理
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, {
value,
timestamp: Date.now(),
ttl
});
}
get(key) {
const item = this.cache.get(key);
if (!item) return undefined;
// 检查是否过期
if (Date.now() - item.timestamp > item.ttl) {
this.cache.delete(key);
return undefined;
}
return item.value;
}
has(key) {
const item = this.cache.get(key);
if (!item) return false;
if (Date.now() - item.timestamp > item.ttl) {
this.cache.delete(key);
return false;
}
return true;
}
delete(key) {
return this.cache.delete(key);
}
clear() {
this.cache.clear();
}
startCleanup() {
setInterval(() => {
const now = Date.now();
for (const [key, item] of this.cache) {
if (now - item.timestamp > item.ttl) {
this.cache.delete(key);
}
}
}, this.interval);
}
}
// 使用示例
const cache = new InMemoryCache({ maxSize: 50, defaultTTL: 60000 });
// API 缓存装饰器
function cacheable(ttl = 300000) {
return function(target, propertyName, descriptor) {
const method = descriptor.value;
descriptor.value = async function(...args) {
const cacheKey = `${propertyName}_${JSON.stringify(args)}`;
// 尝试从缓存获取
const cachedResult = cache.get(cacheKey);
if (cachedResult !== undefined) {
return cachedResult;
}
// 执行原方法
const result = await method.apply(this, args);
// 存入缓存
cache.set(cacheKey, result, ttl);
return result;
};
};
}
// 应用缓存装饰器
class APIService {
@cacheable(60000) // 1分钟缓存
async getUser(userId) {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
@cacheable(300000) // 5分钟缓存
async getConfiguration() {
const response = await fetch('/api/config');
return response.json();
}
}
CDN 缓存策略
CDN 配置最佳实践
javascript
// CDN 缓存头设置中间件
const cdnCacheHeaders = () => {
return (req, res, next) => {
const url = req.url;
// 静态资源 - 长期缓存
if (/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/.test(url)) {
res.set({
'Cache-Control': 'public, max-age=31536000', // 1年
'Vary': 'Accept-Encoding'
});
}
// HTML 文件 - 短期缓存或不缓存
else if (/\.(html?)$/.test(url)) {
res.set({
'Cache-Control': 'public, max-age=300', // 5分钟
'Vary': 'Accept, Accept-Encoding'
});
}
// API 响应 - 根据内容类型设置
else if (/\/api\//.test(url)) {
// 静态 API 数据(如配置)
if (/\/api\/config/.test(url)) {
res.set({
'Cache-Control': 'public, max-age=3600', // 1小时
'Vary': 'Accept'
});
}
// 用户特定数据
else if (/\/api\/user/.test(url)) {
res.set({
'Cache-Control': 'private, max-age=300', // 5分钟,私有缓存
'Vary': 'Accept, Authorization'
});
}
// 其他 API
else {
res.set({
'Cache-Control': 'no-cache',
'Vary': 'Accept, Authorization'
});
}
}
next();
};
};
app.use(cdnCacheHeaders());
数据库查询缓存
Redis 缓存实现
javascript
const redis = require('redis');
class RedisCache {
constructor(options = {}) {
this.client = redis.createClient({
host: options.host || 'localhost',
port: options.port || 6379,
password: options.password,
retry_strategy: (options) => {
if (options.error && options.error.code === 'ECONNREFUSED') {
return new Error('Redis server connection refused');
}
if (options.total_retry_time > 1000 * 60 * 60) {
return new Error('Retry time exhausted');
}
if (options.attempt > 10) {
return undefined;
}
return Math.min(options.attempt * 100, 3000);
}
});
this.client.on('error', (err) => {
console.error('Redis Client Error:', err);
});
this.client.connect();
}
async set(key, value, ttl = 300) { // 5分钟默认TTL
try {
const serializedValue = JSON.stringify(value);
await this.client.setEx(key, ttl, serializedValue);
return true;
} catch (error) {
console.error('Redis set error:', error);
return false;
}
}
async get(key) {
try {
const value = await this.client.get(key);
return value ? JSON.parse(value) : null;
} catch (error) {
console.error('Redis get error:', error);
return null;
}
}
async del(key) {
try {
await this.client.del(key);
return true;
} catch (error) {
console.error('Redis delete error:', error);
return false;
}
}
async exists(key) {
try {
const result = await this.client.exists(key);
return result === 1;
} catch (error) {
console.error('Redis exists error:', error);
return false;
}
}
}
// 数据库查询缓存装饰器
function dbCache(ttl = 300) {
const cache = new RedisCache();
return function(target, propertyName, descriptor) {
const method = descriptor.value;
descriptor.value = async function(...args) {
const cacheKey = `db_${target.constructor.name}_${propertyName}_${JSON.stringify(args)}`;
// 尝试从缓存获取
let result = await cache.get(cacheKey);
if (result !== null) {
return result;
}
// 执行数据库查询
result = await method.apply(this, args);
// 存入缓存
await cache.set(cacheKey, result, ttl);
return result;
};
};
}
// 使用数据库缓存
class UserService {
@dbCache(600) // 10分钟缓存
async getUserById(userId) {
return await db.users.findById(userId);
}
@dbCache(1800) // 30分钟缓存
async getUserStatistics(userId) {
return await db.users.getStatistics(userId);
}
}
缓存失效策略
智能缓存失效
javascript
// 缓存失效管理器
class CacheInvalidationManager {
constructor() {
this.dependencyMap = new Map(); // 记录缓存依赖关系
this.subscribers = []; // 缓存失效通知订阅者
}
// 记录缓存依赖
recordDependency(cacheKey, dependencies) {
if (!this.dependencyMap.has(cacheKey)) {
this.dependencyMap.set(cacheKey, new Set());
}
const cacheDependencies = this.dependencyMap.get(cacheKey);
for (const dep of dependencies) {
cacheDependencies.add(dep);
}
}
// 当某个数据发生变化时,使相关缓存失效
async invalidateByDependency(dependency) {
const cacheKeys = [];
for (const [cacheKey, dependencies] of this.dependencyMap) {
if (dependencies.has(dependency)) {
cacheKeys.push(cacheKey);
}
}
// 使相关缓存失效
await Promise.all(
cacheKeys.map(key => this.invalidateCache(key))
);
// 通知订阅者
this.notifySubscribers(dependency, cacheKeys);
}
async invalidateCache(cacheKey) {
// 这里可以集成具体的缓存实现
console.log(`Invalidating cache: ${cacheKey}`);
// 实际的缓存失效逻辑
await this.performCacheInvalidation(cacheKey);
}
performCacheInvalidation(cacheKey) {
// 实现具体的缓存失效逻辑
// 可能涉及 Redis、内存缓存等多种缓存系统
return Promise.resolve();
}
notifySubscribers(dependency, invalidatedKeys) {
this.subscribers.forEach(subscriber => {
subscriber(dependency, invalidatedKeys);
});
}
subscribe(callback) {
this.subscribers.push(callback);
}
unsubscribe(callback) {
const index = this.subscribers.indexOf(callback);
if (index > -1) {
this.subscribers.splice(index, 1);
}
}
}
// 使用缓存失效管理器
const cacheInvalidation = new CacheInvalidationManager();
// 用户服务中的缓存失效示例
class UserServiceWithInvalidation {
constructor() {
this.cache = new InMemoryCache();
this.cacheInvalidation = cacheInvalidation;
}
async getUser(userId) {
const cacheKey = `user_${userId}`;
const dependency = `user_${userId}`;
// 记录缓存依赖关系
this.cacheInvalidation.recordDependency(cacheKey, [dependency]);
let user = this.cache.get(cacheKey);
if (!user) {
user = await db.users.findById(userId);
this.cache.set(cacheKey, user);
}
return user;
}
async updateUser(userId, updateData) {
const user = await db.users.update(userId, updateData);
// 使相关缓存失效
await this.cacheInvalidation.invalidateByDependency(`user_${userId}`);
return user;
}
}
缓存性能监控
缓存指标收集
javascript
// 缓存性能监控
class CacheMetrics {
constructor() {
this.hits = 0;
this.misses = 0;
this.errors = 0;
this.startTime = Date.now();
}
recordHit() {
this.hits++;
}
recordMiss() {
this.misses++;
}
recordError() {
this.errors++;
}
getHitRate() {
const total = this.hits + this.misses;
return total > 0 ? (this.hits / total) * 100 : 0;
}
getMetrics() {
const total = this.hits + this.misses;
const uptime = Date.now() - this.startTime;
return {
hits: this.hits,
misses: this.misses,
errors: this.errors,
hitRate: this.getHitRate(),
totalRequests: total,
uptime: uptime,
requestsPerSecond: total / (uptime / 1000)
};
}
}
// 带监控的缓存实现
class MonitoredCache extends InMemoryCache {
constructor(options) {
super(options);
this.metrics = new CacheMetrics();
}
get(key) {
const result = super.get(key);
if (result !== undefined) {
this.metrics.recordHit();
} else {
this.metrics.recordMiss();
}
return result;
}
set(key, value, ttl) {
try {
super.set(key, value, ttl);
} catch (error) {
this.metrics.recordError();
throw error;
}
}
getMetrics() {
return this.metrics.getMetrics();
}
}
// 监控端点
app.get('/metrics/cache', (req, res) => {
res.json(monitoredCache.getMetrics());
});
缓存策略选择指南
| 场景 | 推荐缓存策略 | TTL | 说明 |
|---|---|---|---|
| 静态资源 | CDN + 浏览器缓存 | 1年 | 使用文件指纹确保更新 |
| API 配置 | 内存缓存 | 1小时 | 配置相对稳定 |
| 用户数据 | Redis | 5-15分钟 | 用户数据变化频繁 |
| 搜索结果 | Redis | 1-5分钟 | 搜索结果时效性要求高 |
| 会话数据 | Redis | 24小时 | 会话有效期 |
通过实施这些缓存策略,您可以显著提高应用程序的性能和用户体验。