Skip to content
On this page

缓存策略

缓存是提高 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小时配置相对稳定
用户数据Redis5-15分钟用户数据变化频繁
搜索结果Redis1-5分钟搜索结果时效性要求高
会话数据Redis24小时会话有效期

通过实施这些缓存策略,您可以显著提高应用程序的性能和用户体验。