Skip to content

缓存技术

概述

缓存是一种提高系统性能的技术,通过将频繁访问的数据存储在快速访问的存储介质中,减少对慢速存储的访问次数。

核心概念

缓存的作用

  1. 提高响应速度: 减少数据获取时间
  2. 降低系统负载: 减少数据库和外部服务压力
  3. 提升用户体验: 更快的页面加载和操作响应
  4. 节省成本: 减少服务器资源消耗

缓存层次

用户请求 → CDN缓存 → 应用缓存 → 数据库缓存 → 磁盘存储
   ↑         ↑         ↑          ↑
 最快     较快      中等       较慢

缓存策略

1. Cache-Aside (旁路缓存)

javascript
// 读取数据
const getUser = async (userId) => {
  // 1. 先查缓存
  let user = await cache.get(`user:${userId}`);
  
  if (user) {
    console.log('缓存命中');
    return JSON.parse(user);
  }
  
  // 2. 缓存未命中,查数据库
  console.log('缓存未命中');
  user = await User.findById(userId);
  
  if (user) {
    // 3. 写入缓存
    await cache.setex(`user:${userId}`, 3600, JSON.stringify(user));
  }
  
  return user;
};

// 更新数据
const updateUser = async (userId, userData) => {
  // 1. 更新数据库
  const user = await User.findByIdAndUpdate(userId, userData, { new: true });
  
  // 2. 删除缓存
  await cache.del(`user:${userId}`);
  
  return user;
};

2. Write-Through (直写缓存)

javascript
// 直写缓存
const writeThroughCache = {
  async set(key, value, ttl = 3600) {
    // 同时写入缓存和数据库
    await Promise.all([
      cache.setex(key, ttl, JSON.stringify(value)),
      this.writeToDatabase(key, value)
    ]);
  },
  
  async writeToDatabase(key, value) {
    // 根据key确定数据库操作
    if (key.startsWith('user:')) {
      const userId = key.split(':')[1];
      await User.findByIdAndUpdate(userId, value, { upsert: true });
    }
  }
};

// 使用直写缓存
const createUser = async (userData) => {
  const user = new User(userData);
  await user.save();
  
  // 直写缓存
  await writeThroughCache.set(`user:${user._id}`, user);
  
  return user;
};

3. Write-Behind (异步写入)

javascript
// 异步写入缓存
class WriteBehindCache {
  constructor() {
    this.writeQueue = [];
    this.isProcessing = false;
  }
  
  async set(key, value, ttl = 3600) {
    // 立即写入缓存
    await cache.setex(key, ttl, JSON.stringify(value));
    
    // 加入写入队列
    this.writeQueue.push({ key, value, timestamp: Date.now() });
    
    // 异步处理队列
    this.processQueue();
  }
  
  async processQueue() {
    if (this.isProcessing || this.writeQueue.length === 0) {
      return;
    }
    
    this.isProcessing = true;
    
    try {
      const batch = this.writeQueue.splice(0, 100); // 批量处理
      
      for (const item of batch) {
        try {
          await this.writeToDatabase(item.key, item.value);
        } catch (error) {
          console.error('异步写入失败:', error);
          // 重新加入队列
          this.writeQueue.unshift(item);
        }
      }
    } finally {
      this.isProcessing = false;
      
      // 如果队列还有数据,继续处理
      if (this.writeQueue.length > 0) {
        setTimeout(() => this.processQueue(), 1000);
      }
    }
  }
  
  async writeToDatabase(key, value) {
    if (key.startsWith('user:')) {
      const userId = key.split(':')[1];
      await User.findByIdAndUpdate(userId, value, { upsert: true });
    }
  }
}

4. Refresh-Ahead (提前刷新)

javascript
// 提前刷新缓存
class RefreshAheadCache {
  constructor(refreshThreshold = 0.8) {
    this.refreshThreshold = refreshThreshold; // 80%过期时刷新
  }
  
  async get(key, fetchFunction, ttl = 3600) {
    const cached = await cache.get(key);
    
    if (!cached) {
      // 缓存未命中,获取数据并缓存
      const data = await fetchFunction();
      await cache.setex(key, ttl, JSON.stringify(data));
      return data;
    }
    
    const parsed = JSON.parse(cached);
    const ttlInfo = await cache.ttl(key);
    
    // 检查是否需要提前刷新
    if (ttlInfo < ttl * (1 - this.refreshThreshold)) {
      // 异步刷新缓存
      this.refreshCache(key, fetchFunction, ttl);
    }
    
    return parsed;
  }
  
  async refreshCache(key, fetchFunction, ttl) {
    try {
      const data = await fetchFunction();
      await cache.setex(key, ttl, JSON.stringify(data));
      console.log(`缓存已刷新: ${key}`);
    } catch (error) {
      console.error(`缓存刷新失败: ${key}`, error);
    }
  }
}

// 使用提前刷新缓存
const userCache = new RefreshAheadCache();

const getUser = async (userId) => {
  return await userCache.get(
    `user:${userId}`,
    () => User.findById(userId),
    3600
  );
};

Redis缓存

Redis基础操作

javascript
const redis = require('redis');
const client = redis.createClient({
  host: 'localhost',
  port: 6379,
  password: 'your-password'
});

// 连接Redis
client.on('connect', () => {
  console.log('Redis连接成功');
});

client.on('error', (err) => {
  console.error('Redis连接错误:', err);
});

// 基础操作
class RedisCache {
  async set(key, value, ttl = 3600) {
    return await client.setex(key, ttl, JSON.stringify(value));
  }
  
  async get(key) {
    const value = await client.get(key);
    return value ? JSON.parse(value) : null;
  }
  
  async del(key) {
    return await client.del(key);
  }
  
  async exists(key) {
    return await client.exists(key);
  }
  
  async expire(key, ttl) {
    return await client.expire(key, ttl);
  }
  
  async ttl(key) {
    return await client.ttl(key);
  }
}

Redis数据结构

字符串 (String)

javascript
// 字符串操作
await client.set('user:123:name', '张三');
await client.setex('user:123:name', 3600, '张三'); // 设置过期时间
const name = await client.get('user:123:name');

// 计数器
await client.incr('page:views');
await client.incrby('page:views', 10);
const views = await client.get('page:views');

哈希 (Hash)

javascript
// 哈希操作
await client.hset('user:123', {
  name: '张三',
  email: '[email protected]',
  age: '25'
});

const user = await client.hgetall('user:123');
const name = await client.hget('user:123', 'name');
await client.hdel('user:123', 'age');

列表 (List)

javascript
// 列表操作
await client.lpush('recent:users', 'user:123');
await client.rpush('recent:users', 'user:456');
const users = await client.lrange('recent:users', 0, 9); // 获取最近10个用户
await client.ltrim('recent:users', 0, 99); // 只保留最近100个

集合 (Set)

javascript
// 集合操作
await client.sadd('user:123:followers', 'user:456', 'user:789');
const followers = await client.smembers('user:123:followers');
const count = await client.scard('user:123:followers');
const isFollower = await client.sismember('user:123:followers', 'user:456');

有序集合 (Sorted Set)

javascript
// 有序集合操作
await client.zadd('leaderboard', {
  'user:123': 1000,
  'user:456': 1500,
  'user:789': 800
});

const topUsers = await client.zrevrange('leaderboard', 0, 9, 'WITHSCORES');
const rank = await client.zrevrank('leaderboard', 'user:123');
const score = await client.zscore('leaderboard', 'user:123');

Redis集群和哨兵

javascript
// Redis集群配置
const Redis = require('ioredis');

const cluster = new Redis.Cluster([
  {
    host: 'redis-node-1',
    port: 6379
  },
  {
    host: 'redis-node-2',
    port: 6379
  },
  {
    host: 'redis-node-3',
    port: 6379
  }
]);

// Redis哨兵配置
const sentinel = new Redis({
  sentinels: [
    { host: 'sentinel-1', port: 26379 },
    { host: 'sentinel-2', port: 26379 },
    { host: 'sentinel-3', port: 26379 }
  ],
  name: 'mymaster'
});

内存缓存

Node.js内存缓存

javascript
// 简单的内存缓存
class MemoryCache {
  constructor() {
    this.cache = new Map();
    this.timers = new Map();
  }
  
  set(key, value, ttl = 3600000) { // 默认1小时
    // 清除现有定时器
    if (this.timers.has(key)) {
      clearTimeout(this.timers.get(key));
    }
    
    // 设置缓存
    this.cache.set(key, {
      value,
      timestamp: Date.now(),
      ttl
    });
    
    // 设置过期定时器
    const timer = setTimeout(() => {
      this.delete(key);
    }, ttl);
    
    this.timers.set(key, timer);
  }
  
  get(key) {
    const item = this.cache.get(key);
    
    if (!item) {
      return null;
    }
    
    // 检查是否过期
    if (Date.now() - item.timestamp > item.ttl) {
      this.delete(key);
      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();
    
    for (const timer of this.timers.values()) {
      clearTimeout(timer);
    }
    this.timers.clear();
  }
  
  size() {
    return this.cache.size;
  }
  
  keys() {
    return Array.from(this.cache.keys());
  }
}

// 使用内存缓存
const cache = new MemoryCache();
cache.set('user:123', { name: '张三', email: '[email protected]' }, 300000); // 5分钟
const user = cache.get('user:123');

LRU缓存

javascript
// LRU (Least Recently Used) 缓存
class LRUCache {
  constructor(capacity = 100) {
    this.capacity = capacity;
    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.capacity) {
      // 删除最久未使用的项
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    
    this.cache.set(key, value);
  }
  
  delete(key) {
    return this.cache.delete(key);
  }
  
  clear() {
    this.cache.clear();
  }
  
  size() {
    return this.cache.size;
  }
}

缓存模式

1. 缓存穿透

javascript
// 缓存穿透防护
class CachePenetrationProtection {
  constructor() {
    this.cache = new Map();
    this.bloomFilter = new Set(); // 简化的布隆过滤器
  }
  
  async get(key, fetchFunction) {
    // 检查布隆过滤器
    if (!this.bloomFilter.has(key)) {
      return null; // 数据不存在
    }
    
    // 检查缓存
    let value = this.cache.get(key);
    
    if (value === undefined) {
      // 缓存未命中,获取数据
      value = await fetchFunction();
      
      if (value !== null) {
        // 数据存在,加入缓存和布隆过滤器
        this.cache.set(key, value);
        this.bloomFilter.add(key);
      } else {
        // 数据不存在,加入布隆过滤器防止重复查询
        this.bloomFilter.add(key);
        // 设置空值缓存,避免重复查询数据库
        this.cache.set(key, null);
      }
    }
    
    return value;
  }
  
  // 预热布隆过滤器
  async warmupBloomFilter(keys) {
    for (const key of keys) {
      this.bloomFilter.add(key);
    }
  }
}

2. 缓存雪崩

javascript
// 缓存雪崩防护
class CacheAvalancheProtection {
  constructor() {
    this.cache = new Map();
  }
  
  async get(key, fetchFunction, baseTtl = 3600000) {
    let item = this.cache.get(key);
    
    if (!item || Date.now() > item.expireTime) {
      // 缓存未命中或已过期
      const value = await fetchFunction();
      
      // 添加随机过期时间,避免同时过期
      const randomTtl = baseTtl + Math.random() * 300000; // 增加0-5分钟随机时间
      
      item = {
        value,
        expireTime: Date.now() + randomTtl
      };
      
      this.cache.set(key, item);
    }
    
    return item.value;
  }
}

3. 缓存击穿

javascript
// 缓存击穿防护
class CacheBreakdownProtection {
  constructor() {
    this.cache = new Map();
    this.locks = new Map();
  }
  
  async get(key, fetchFunction, ttl = 3600000) {
    let item = this.cache.get(key);
    
    if (!item || Date.now() > item.expireTime) {
      // 获取或创建锁
      let lock = this.locks.get(key);
      if (!lock) {
        lock = new Promise((resolve) => {
          this.locks.set(key, resolve);
        });
      }
      
      // 等待锁释放
      await lock;
      
      // 再次检查缓存(双重检查锁定)
      item = this.cache.get(key);
      if (!item || Date.now() > item.expireTime) {
        // 获取数据
        const value = await fetchFunction();
        
        item = {
          value,
          expireTime: Date.now() + ttl
        };
        
        this.cache.set(key, item);
        
        // 释放锁
        this.locks.delete(key);
      }
    }
    
    return item.value;
  }
}

缓存监控

缓存命中率监控

javascript
// 缓存监控
class CacheMonitor {
  constructor() {
    this.stats = {
      hits: 0,
      misses: 0,
      sets: 0,
      deletes: 0
    };
  }
  
  get hitRate() {
    const total = this.stats.hits + this.stats.misses;
    return total > 0 ? this.stats.hits / total : 0;
  }
  
  recordHit() {
    this.stats.hits++;
  }
  
  recordMiss() {
    this.stats.misses++;
  }
  
  recordSet() {
    this.stats.sets++;
  }
  
  recordDelete() {
    this.stats.deletes++;
  }
  
  getStats() {
    return {
      ...this.stats,
      hitRate: this.hitRate,
      totalRequests: this.stats.hits + this.stats.misses
    };
  }
  
  reset() {
    this.stats = {
      hits: 0,
      misses: 0,
      sets: 0,
      deletes: 0
    };
  }
}

// 使用监控的缓存
const monitor = new CacheMonitor();

class MonitoredCache {
  async get(key, fetchFunction) {
    const cached = await cache.get(key);
    
    if (cached) {
      monitor.recordHit();
      return cached;
    }
    
    monitor.recordMiss();
    const value = await fetchFunction();
    
    if (value !== null) {
      await cache.set(key, value);
      monitor.recordSet();
    }
    
    return value;
  }
}

缓存预热

javascript
// 缓存预热
class CacheWarmup {
  constructor(cache) {
    this.cache = cache;
  }
  
  async warmupUserCache(userIds) {
    console.log('开始预热用户缓存...');
    
    const batchSize = 100;
    const batches = [];
    
    for (let i = 0; i < userIds.length; i += batchSize) {
      batches.push(userIds.slice(i, i + batchSize));
    }
    
    for (const batch of batches) {
      await Promise.all(
        batch.map(async (userId) => {
          try {
            const user = await User.findById(userId);
            if (user) {
              await this.cache.set(`user:${userId}`, user);
            }
          } catch (error) {
            console.error(`预热用户缓存失败: ${userId}`, error);
          }
        })
      );
      
      // 避免过载
      await new Promise(resolve => setTimeout(resolve, 100));
    }
    
    console.log('用户缓存预热完成');
  }
  
  async warmupProductCache(productIds) {
    console.log('开始预热商品缓存...');
    
    const products = await Product.find({
      _id: { $in: productIds }
    });
    
    await Promise.all(
      products.map(async (product) => {
        await this.cache.set(`product:${product._id}`, product);
      })
    );
    
    console.log('商品缓存预热完成');
  }
}

最佳实践

1. 缓存键设计

javascript
// 好的缓存键设计
const cacheKeys = {
  // 用户相关
  user: (userId) => `user:${userId}`,
  userProfile: (userId) => `user:${userId}:profile`,
  userPermissions: (userId) => `user:${userId}:permissions`,
  
  // 商品相关
  product: (productId) => `product:${productId}`,
  productList: (category, page) => `products:${category}:page:${page}`,
  productSearch: (query) => `search:${query}`,
  
  // 订单相关
  order: (orderId) => `order:${orderId}`,
  userOrders: (userId, status) => `user:${userId}:orders:${status}`,
  
  // 统计数据
  stats: (type, date) => `stats:${type}:${date}`,
  leaderboard: (type) => `leaderboard:${type}`
};

2. 缓存失效策略

javascript
// 缓存失效策略
class CacheInvalidation {
  constructor(cache) {
    this.cache = cache;
  }
  
  // 精确失效
  async invalidateUser(userId) {
    const keys = [
      `user:${userId}`,
      `user:${userId}:profile`,
      `user:${userId}:permissions`
    ];
    
    await Promise.all(
      keys.map(key => this.cache.delete(key))
    );
  }
  
  // 模式失效
  async invalidateUserPattern(userId) {
    const pattern = `user:${userId}:*`;
    const keys = await this.cache.keys(pattern);
    
    await Promise.all(
      keys.map(key => this.cache.delete(key))
    );
  }
  
  // 级联失效
  async invalidateCascade(userId) {
    // 失效用户相关缓存
    await this.invalidateUser(userId);
    
    // 失效用户订单缓存
    await this.invalidateUserPattern(`user:${userId}:orders`);
    
    // 失效统计数据缓存
    await this.cache.delete('stats:users');
    await this.cache.delete('stats:orders');
  }
}

3. 缓存配置

javascript
// 缓存配置
const cacheConfig = {
  // 用户缓存
  user: {
    ttl: 3600, // 1小时
    maxSize: 10000,
    strategy: 'lru'
  },
  
  // 商品缓存
  product: {
    ttl: 1800, // 30分钟
    maxSize: 50000,
    strategy: 'lru'
  },
  
  // 统计数据缓存
  stats: {
    ttl: 300, // 5分钟
    maxSize: 1000,
    strategy: 'fifo'
  },
  
  // 搜索结果缓存
  search: {
    ttl: 600, // 10分钟
    maxSize: 5000,
    strategy: 'lru'
  }
};

// 根据配置创建缓存
class ConfigurableCache {
  constructor(config) {
    this.configs = config;
    this.caches = new Map();
  }
  
  getCache(type) {
    if (!this.caches.has(type)) {
      const config = this.configs[type];
      this.caches.set(type, new LRUCache(config.maxSize));
    }
    return this.caches.get(type);
  }
  
  async get(type, key, fetchFunction) {
    const cache = this.getCache(type);
    const config = this.configs[type];
    
    let item = cache.get(key);
    
    if (!item || Date.now() > item.expireTime) {
      const value = await fetchFunction();
      
      item = {
        value,
        expireTime: Date.now() + config.ttl * 1000
      };
      
      cache.set(key, item);
    }
    
    return item.value;
  }
}

总结

缓存技术是提高系统性能的重要手段,需要综合考虑:

  1. 缓存策略: 选择合适的缓存策略(Cache-Aside、Write-Through等)
  2. 缓存存储: 选择合适的存储介质(内存、Redis等)
  3. 缓存模式: 防止缓存穿透、雪崩、击穿等问题
  4. 缓存监控: 监控缓存命中率和性能指标
  5. 缓存优化: 合理设计缓存键和失效策略

良好的缓存设计能够显著提升系统性能和用户体验。