缓存失效策略 CacheInvalidationStrategies

本技能涉及缓存失效的不同模式和策略,包括基于时间的过期、事件驱动失效、写入时缓存、写后缓存、缓存旁路、读穿缓存等,以及如何实现这些策略和监控缓存命中率。关键词包括缓存失效、数据一致性、性能优化。

后端开发 0 次安装 0 次浏览 更新于 3/5/2026

缓存失效策略

概览

“计算机科学中只有两件难事:缓存失效和命名。” - Phil Karlton 缓存失效是当底层数据改变时移除或更新缓存数据的过程。如果处理不当,会导致用户收到过时的数据。如果处理得当,它确保了数据一致性的同时保持了性能优势。

前置知识

  • 理解缓存概念和好处
  • 了解Redis或类似的缓存技术
  • 熟悉数据库操作和数据一致性
  • 基本了解分布式系统

关键概念

为什么缓存失效这么难

缓存失效之所以具有挑战性,因为:

  1. 多层缓存:数据可能在多个级别上被缓存(浏览器、CDN、应用缓存、数据库缓存)
  2. 复杂依赖:缓存的数据可能依赖多个数据源
  3. 时间问题:在缓存更新时可能会发生变更
  4. 分布式系统:多个服务器可能有不一致的缓存状态
  5. 性能权衡:激进的失效影响性能,保守的失效可能导致过时数据

缓存失效模式

基于时间的过期(TTL)

最简单的方法 - 缓存条目在固定时间后过期。 优点:

  • 简单实现
  • 不需要失效逻辑
  • 适用于变化缓慢的数据 缺点:
  • 可能在TTL过期前提供过时数据
  • 在未改变的数据上浪费缓存空间
  • 难以选择最佳TTL

事件驱动失效

数据改变时立即使缓存失效。 优点:

  • 立即一致性
  • 不提供过时数据
  • 精确控制 缺点:
  • 实现更复杂
  • 需要事件跟踪
  • 可能影响性能

写入时缓存

同时向缓存和数据库写入数据。 优点:

  • 缓存始终是最新的
  • 简单的读取逻辑 缺点:
  • 写入变慢(缓存+数据库)
  • 可能会写入从未读取的数据

写后(写回)缓存

首先写入缓存,然后异步持久化到数据库。 优点:

  • 快速写入(仅缓存)
  • 可以批量数据库写入 缺点:
  • 如果缓存失败则有数据丢失风险
  • 实现复杂
  • 最终一致性

缓存旁路(懒加载)

首先检查缓存,如果未命中则从数据库加载。 优点:

  • 简单实现
  • 仅缓存访问过的数据
  • 适用于读多写少的工作负载 缺点:
  • 缓存踩踏风险
  • 第一个请求较慢
  • 直到TTL过期都是过时数据

读穿缓存

缓存抽象处理从数据库加载。 优点:

  • 清晰的关注点分离
  • 集中式缓存逻辑
  • 易于添加功能 缺点:
  • 稍微复杂一些
  • 需要自定义缓存实现

实施指南

基于时间的过期(TTL)

// 设置缓存和TTL
await cache.set('user:123', userData, { ttl: 3600 });  // 1小时

// 从缓存中获取
const cached = await cache.get('user:123');
if (cached) {
  return cached;
}

// 缓存未命中 - 从数据库获取
const data = await db.users.findById(123);
await cache.set('user:123', data, { ttl: 3600 });
return data;

选择TTL:

// 基于数据变更频率的自适应TTL
function calculateTTL(dataType, lastModified) {
  const age = Date.now() - lastModified;

  switch (dataType) {
    case 'user_profile':
      return 3600;  // 1小时 - 很少变化
    case 'stock_price':
      return 5;     // 5秒 - 频繁变化
    case 'news_feed':
      return 300;    // 5分钟 - 适度变化
    default:
      return 600;    // 10分钟默认
  }
}

// 使用
const userData = await db.users.findById(123);
const ttl = calculateTTL('user_profile', userData.updatedAt);
await cache.set(`user:${userData.id}`, userData, { ttl });

事件驱动失效

// 更新用户并使缓存失效
async function updateUser(userId, updates) {
  // 更新数据库
  const user = await db.users.update(userId, updates);

  // 使缓存失效
  await cache.del(`user:${userId}`);

  return user;
}

// 删除用户并使缓存失效
async function deleteUser(userId) {
  await db.users.delete(userId);
  await cache.del(`user:${userId}`);
}

事件总线模式:

const EventEmitter = require('events');

class CacheInvalidator extends EventEmitter {
  constructor() {
    super();
    this.setupListeners();
  }

  setupListeners() {
    // 监听数据变更事件
    this.on('user:updated', async ({ userId }) => {
      await cache.del(`user:${userId}`);
      await cache.del(`user:${userId}:profile`);
      await cache.del(`user:${userId}:settings`);
    });

    this.on('post:created', async ({ userId }) => {
      // 使用户帖子缓存失效
      await cache.del(`user:${userId}:posts`);
      // 使Feed缓存失效
      await cache.del('feed:recent');
    });

    this.on('post:deleted', async ({ userId, postId }) => {
      await cache.del(`user:${userId}:posts`);
      await cache.del(`post:${postId}`);
    });
  }
}

// 使用
const invalidator = new CacheInvalidator();

async function createPost(userId, content) {
  const post = await db.posts.create({ userId, content });

  // 发出事件
  invalidator.emit('post:created', { userId, postId: post.id });

  return post;
}

写入时缓存

async function updateUser(userId, updates) {
  const user = await db.users.update(userId, updates);

  // 写入缓存
  await cache.set(`user:${userId}`, user, { ttl: 3600 });

  return user;
}

// 读取总是命中缓存
async function getUser(userId) {
  const cached = await cache.get(`user:${userId}`);
  if (cached) {
    return cached;
  }

  const user = await db.users.findById(userId);
  await cache.set(`user:${userId}`, user, { ttl: 3600 });
  return user;
}

写后(写回)缓存

class WriteBehindCache {
  constructor() {
    this.writeQueue = [];
    this.processing = false;
    this.startProcessing();
  }

  async set(key, value, options) {
    // 立即写入缓存
    await cache.set(key, value, options);

    // 排队数据库写入
    this.writeQueue.push({ key, value, timestamp: Date.now() });
  }

  startProcessing() {
    setInterval(async () => {
      if (this.writeQueue.length === 0 || this.processing) return;

      this.processing = true;

      try {
        const batch = this.writeQueue.splice(0, 100);  // 批量处理
        await this.writeBatchToDatabase(batch);
      } catch (error) {
        console.error('写回错误:', error);
        // 重新排队失败的写入
        this.writeQueue.unshift(...batch);
      } finally {
        this.processing = false;
      }
    }, 100);  // 每100ms处理一次
  }

  async writeBatchToDatabase(batch) {
    // 批量写入数据库
    const operations = batch.map(({ key, value }) => {
      const [type, id] = key.split(':');
      return db[type].update(id, value);
    });

    await Promise.all(operations);
  }
}

缓存旁路(懒加载)

async function getUser(userId) {
  // 检查缓存
  const cached = await cache.get(`user:${userId}`);
  if (cached) {
    return cached;
  }

  // 缓存未命中 - 从数据库加载
  const user = await db.users.findById(userId);

  // 填充缓存
  await cache.set(`user:${userId}`, user, { ttl: 3600 });

  return user;
}

缓存踩踏防止:

async function getUserWithStampedeProtection(userId) {
  const cacheKey = `user:${userId}`;
  const lockKey = `lock:${cacheKey}`;

  // 检查缓存
  const cached = await cache.get(cacheKey);
  if (cached) {
    return cached;
  }

  // 尝试获取锁
  const lock = await cache.set(lockKey, '1', {
    ttl: 10,  // 锁在10s后过期
    nx: true  // 仅当不存在时设置
  });

  if (lock) {
    // 我们持有锁 - 从数据库加载
    try {
      const user = await db.users.findById(userId);
      await cache.set(cacheKey, user, { ttl: 3600 });
      await cache.del(lockKey);
      return user;
    } catch (error) {
      await cache.del(lockKey);
      throw error;
    }
  } else {
    // 另一个请求正在加载 - 等待并重试
    await sleep(100);
    return getUserWithStampedeProtection(userId);
  }
}

读穿缓存

class ReadThroughCache {
  constructor(loader) {
    this.loader = loader;
  }

  async get(key) {
    // 检查缓存
    const cached = await cache.get(key);
    if (cached) {
      return cached;
    }

    // 缓存未命中 - 使用loader
    const value = await this.loader(key);

    // 填充缓存
    await cache.set(key, value, { ttl: 3600 });

    return value;
  }
}

// 使用
const userCache = new ReadThroughCache(async (key) => {
  const userId = key.split(':')[1];
  return await db.users.findById(userId);
});

const user = await userCache.get('user:123');

失效策略

清除(删除特定键)

移除特定的缓存条目。

// 简单清除
await cache.del('user:123');

// 多个键
await cache.del('user:123', 'user:123:profile', 'user:123:settings');

// 基于模式的清除(Redis)
await cache.del('user:123:*');

何时使用:

  • 单个数据源变更
  • 简单的键结构
  • 需要精确失效

禁令(基于模式的失效)

移除所有匹配模式的缓存条目。

// Redis基于模式的删除
async function banPattern(pattern) {
  const keys = await cache.keys(pattern);
  if (keys.length > 0) {
    await cache.del(...keys);
  }
}

// 使用
await banPattern('user:123:*');  // 删除用户123的所有缓存
await banPattern('feed:*');       // 删除所有Feed缓存

基于标签的失效:

class TaggedCache {
  constructor() {
    this.keyTags = new Map();  // key -> 标签集合
    this.tagKeys = new Map();  // 标签 -> 键集合
  }

  async set(key, value, options = {}) {
    const tags = options.tags || [];

    // 存储值
    await cache.set(key, value, options);

    // 更新标签映射
    this.keyTags.set(key, new Set(tags));

    for (const tag of tags) {
      if (!this.tagKeys.has(tag)) {
        this.tagKeys.set(tag, new Set());
      }
      this.tagKeys.get(tag).add(key);
    }
  }

  async invalidateByTag(tag) {
    const keys = this.tagKeys.get(tag);
    if (!keys) return;

    // 删除所有具有此标签的键
    const keyArray = Array.from(keys);
    await cache.del(...keyArray);

    // 清理映射
    for (const key of keyArray) {
      const tags = this.keyTags.get(key);
      tags.delete(tag);
      if (tags.size === 0) {
        this.keyTags.delete(key);
      }
    }

    this.tagKeys.delete(tag);
  }
}

// 使用
const taggedCache = new TaggedCache();

// 带标签的缓存
await taggedCache.set('user:123', userData, {
  tags: ['user', 'profile', 'premium']
});

await taggedCache.set('user:456', userData, {
  tags: ['user', 'profile']
});

// 使所有用户缓存失效
await taggedCache.invalidateByTag('user');

// 仅使高级用户缓存失效
await taggedCache.invalidateByTag('premium');

刷新(原地更新)

用新数据更新缓存而不移除它。

async function refreshUser(userId) {
  // 获取新数据
  const user = await db.users.findById(userId);

  // 原地更新缓存
  await cache.set(`user:${userId}`, user, { ttl: 3600 });

  return user;
}

// 后台刷新
async function backgroundRefresh(key, loader) {
  try {
    const freshData = await loader(key);
    await cache.set(key, freshData, { ttl: 3600 });
  } catch (error) {
    console.error('后台刷新失败:', error);
  }
}

// 到期前主动刷新
async function getWithProactiveRefresh(key, loader) {
  const cached = await cache.get(key);

  if (cached) {
    const ttl = await cache.ttl(key);

    // 如果TTL低于阈值(例如,剩余10%)则刷新
    if (ttl < 360) {  // 3600 * 0.1
      backgroundRefresh(key, loader);
    }

    return cached;
  }

  // 缓存未命中
  const data = await loader(key);
  await cache.set(key, data, { ttl: 3600 });
  return data;
}

软清除(提供过时服务同时重新验证)

标记缓存为过时但继续提供服务,同时刷新。

class SoftPurgeCache {
  async get(key) {
    const cached = await cache.get(key);

    if (!cached) {
      return null;
    }

    // 检查是否标记为软清除
    if (cached._stale) {
      // 触发后台刷新
      this.backgroundRefresh(key);

      // 返回过时数据
      return cached._data;
    }

    return cached;
  }

  async softPurge(key) {
    const cached = await cache.get(key);

    if (cached) {
      // 标记为过时但保留数据
      await cache.set(key, {
        _stale: true,
        _data: cached,
        _purgedAt: Date.now(),
      });
    }
  }

  async backgroundRefresh(key) {
    const cached = await cache.get(key);

    // 检查是否已经在刷新
    if (cached && cached._refreshing) {
      return;
    }

    // 标记为刷新中
    await cache.set(key, {
      ...cached,
      _refreshing: true,
    });

    try {
      const freshData = await this.loader(key);
      await cache.set(key, freshData, { ttl: 3600 });
    } catch (error) {
      console.error('刷新失败:', error);
      // 移除刷新标志
      await cache.set(key, { ...cached, _refreshing: false });
    }
  }
}

缓存踩踏防止

基于锁的防止

async function getWithLock(key, loader, ttl = 3600) {
  const cached = await cache.get(key);
  if (cached) {
    return cached;
  }

  const lockKey = `lock:${key}`;
  const lockValue = Date.now().toString();

  // 尝试获取锁
  const acquired = await cache.set(lockKey, lockValue, {
    ttl: 10,  // 锁在10s后过期
    nx: true,
  });

  if (acquired) {
    // 我们持有锁
    try {
      const value = await loader(key);
      await cache.set(key, value, { ttl });
      await cache.del(lockKey);
      return value;
    } catch (error) {
      await cache.del(lockKey);
      throw error;
    }
  } else {
    // 等待锁持有者
    await sleep(50);

    // 再次检查缓存
    const cached = await cache.get(key);
    if (cached) {
      return cached;
    }

    // 仍然没有缓存,再试一次
    return getWithLock(key, loader, ttl);
  }
}

请求合并

class RequestCoalescer {
  constructor() {
    this.pendingRequests = new Map();
  }

  async get(key, loader) {
    // 检查是否有待处理的请求
    if (this.pendingRequests.has(key)) {
      return await this.pendingRequests.get(key);
    }

    // 创建新的promise
    const promise = this.loadAndCache(key, loader);
    this.pendingRequests.set(key, promise);

    try {
      return await promise;
    } finally {
      this.pendingRequests.delete(key);
    }
  }

  async loadAndCache(key, loader) {
    const cached = await cache.get(key);
    if (cached) {
      return cached;
    }

    const value = await loader(key);
    await cache.set(key, value, { ttl: 3600 });
    return value;
  }
}

// 使用
const coalescer = new RequestCoalescer();

async function getUser(userId) {
  return await coalescer.get(`user:${userId}`, async (key) => {
    const id = key.split(':')[1];
    return await db.users.findById(id);
  });
}

分布式缓存失效

Pub/Sub模式

const Redis = require('ioredis');

class DistributedCacheInvalidator {
  constructor() {
    this.publisher = new Redis();
    this.subscriber = new Redis();
    this.setupSubscriber();
  }

  setupSubscriber() {
    this.subscriber.psubscribe('cache:*');

    this.subscriber.on('pmessage', (pattern, channel, message) => {
      const [action, key] = channel.split(':').slice(1);

      switch (action) {
        case 'invalidate':
          cache.del(key);
          break;
        case 'invalidate_pattern':
          this.invalidatePattern(message);
          break;
      }
    });
  }

  async invalidate(key) {
    // 使本地缓存失效
    await cache.del(key);

    // 通知其他实例
    await this.publisher.publish(`cache:invalidate:${key}`, '');
  }

  async invalidatePattern(pattern) {
    const keys = await cache.keys(pattern);
    if (keys.length > 0) {
      await cache.del(...keys);
    }
  }

  async invalidatePatternDistributed(pattern) {
    // 使本地缓存失效
    await this.invalidatePattern(pattern);

    // 通知其他实例
    await this.publisher.publish(`cache:invalidate_pattern:${pattern}`, '');
  }
}

数据库变更数据捕获(CDC)

const { DebeziumConnector } = require('debezium-connector');

class CDCInvalidator {
  constructor() {
    this.connector = new DebeziumConnector({
      bootstrapServers: 'localhost:9092',
      topic: 'dbserver1.inventory.users',
    });

    this.setupListener();
  }

  setupListener() {
    this.connector.on('change', async (event) => {
      const { op, before, after } = event;

      switch (op) {
        case 'u':  // 更新
        case 'd':  // 删除
          const userId = before.id;
          await cache.del(`user:${userId}`);
          await cache.del(`user:${userId}:profile`);
          break;

        case 'c':  // 创建
          const newUserId = after.id;
          // 可选预热缓存
          await cache.set(`user:${newUserId}`, after, { ttl: 3600 });
          break;
      }
    });
  }
}

缓存标签和分组

层次化标签

class HierarchicalCache {
  constructor() {
    this.tagHierarchy = new Map();  // 标签 -> 父标签
  }

  defineTag(tag, parentTags = []) {
    this.tagHierarchy.set(tag, parentTags);
  }

  async set(key, value, options = {}) {
    const tags = options.tags || [];

    // 解析所有父标签
    const allTags = new Set(tags);
    for (const tag of tags) {
      const parents = this.tagHierarchy.get(tag) || [];
      parents.forEach(parent => allTags.add(parent));
    }

    // 用所有标签存储
    await cache.set(key, value, { ...options, tags: Array.from(allTags) });
  }

  async invalidateTag(tag) {
    // 获取所有具有此标签或其子标签的键
    const keys = await this.getKeysByTag(tag);
    await cache.del(...keys);
  }

  async getKeysByTag(tag) {
    const keys = new Set();

    // 直接标签匹配
    const directKeys = await this.tagKeys.get(tag) || [];
    directKeys.forEach(key => keys.add(key));

    // 子标签匹配
    for (const [childTag, parentTags] of this.tagHierarchy) {
      if (parentTags.includes(tag)) {
        const childKeys = await this.tagKeys.get(childTag) || [];
        childKeys.forEach(key => keys.add(key));
      }
    }

    return Array.from(keys);
  }
}

// 使用
const cache = new HierarchicalCache();

// 定义标签层次结构
cache.defineTag('user', ['data']);
cache.defineTag('post', ['data']);
cache.defineTag('feed', ['data', 'aggregated']);

// 带标签的缓存
await cache.set('user:123', userData, { tags: ['user'] });
await cache.set('post:456', postData, { tags: ['post'] });
await cache.set('feed:recent', feedData, { tags: ['feed'] });

// 使所有数据失效
await cache.invalidateTag('data');

版本化缓存键

在缓存键中包含版本以简化失效。

class VersionedCache {
  constructor() {
    this.versions = new Map();  // 前缀 -> 版本号
  }

  async get(prefix, key) {
    const version = this.getVersion(prefix);
    const versionedKey = `${prefix}:v${version}:${key}`;
    return await cache.get(versionedKey);
  }

  async set(prefix, key, value, options) {
    const version = this.getVersion(prefix);
    const versionedKey = `${prefix}:v${version}:${key}`;
    return await cache.set(versionedKey, value, options);
  }

  getVersion(prefix) {
    if (!this.versions.has(prefix)) {
      this.versions.set(prefix, 1);
    }
    return this.versions.get(prefix);
  }

  async invalidate(prefix) {
    // 增加版本
    const currentVersion = this.getVersion(prefix);
    this.versions.set(prefix, currentVersion + 1);

    // 旧键将自然过期
    // 可选,立即删除旧键
    await this.deleteOldVersionKeys(prefix, currentVersion);
  }

  async deleteOldVersionKeys(prefix, oldVersion) {
    const pattern = `${prefix}:v${oldVersion}:*`;
    const keys = await cache.keys(pattern);
    if (keys.length > 0) {
      await cache.del(...keys);
    }
  }
}

// 使用
const cache = new VersionedCache();

// 设置缓存
await cache.set('user', '123', userData, { ttl: 3600 });
await cache.get('user', '123');  // user:v1:123

// 使所有用户缓存失效
await cache.invalidate('user');

// 新版本
await cache.set('user', '123', userData, { ttl: 3600 });
await cache.get('user', '123');  // user:v2:123

缓存预热策略

按需预热

首次访问时预热缓存。

async function getWithWarmup(key, loader) {
  const cached = await cache.get(key);

  if (cached) {
    return cached;
  }

  // 缓存未命中 - 加载并预热
  const value = await loader(key);
  await cache.set(key, value, { ttl: 3600 });

  // 预热相关缓存
  await warmRelatedCaches(value);

  return value;
}

async function warmRelatedCaches(user) {
  // 预热用户的帖子
  const posts = await db.posts.findByUserId(user.id);
  await cache.set(`user:${user.id}:posts`, posts, { ttl: 3600 });

  // 预热用户的朋友
  const friends = await db.friends.findByUserId(user.id);
  await cache.set(`user:${user.id}:friends`, friends, { ttl: 3600 });
}

计划预热

按计划预热缓存。

class CacheWarmer {
  constructor() {
    this.jobs = new Map();
  }

  schedule(key, loader, interval) {
    const job = setInterval(async () => {
      try {
        const value = await loader(key);
        await cache.set(key, value, { ttl: interval * 2 });
      } catch (error) {
        console.error('缓存预热失败:', error);
      }
    }, interval);

    this.jobs.set(key, job);

    // 初始预热
    loader(key).then(value => {
      cache.set(key, value, { ttl: interval * 2 });
    });
  }

  unschedule(key) {
    const job = this.jobs.get(key);
    if (job) {
      clearInterval(job);
      this.jobs.delete(key);
    }
  }
}

// 使用
const warmer = new CacheWarmer();

// 每小时预热用户缓存
warmer.schedule('user:123', async () => {
  return await db.users.findById(123);
}, 3600000);

预测性预热

基于访问模式预热缓存。

class PredictiveCacheWarmer {
  constructor() {
    this.accessPatterns = new Map();  // key -> 访问时间戳
    this.predictions = new Map();    // key -> 下次访问预测
  }

  recordAccess(key) {
    const now = Date.now();
    const timestamps = this.accessPatterns.get(key) || [];
    timestamps.push(now);

    // 只保留最后100次访问
    if (timestamps.length > 100) {
      timestamps.shift();
    }

    this.accessPatterns.set(key, timestamps);
    this.updatePrediction(key);
  }

  updatePrediction(key) {
    const timestamps = this.accessPatterns.get(key);
    if (timestamps.length < 2) return;

    // 计算平均间隔
    let totalInterval = 0;
    for (let i = 1; i < timestamps.length; i++) {
      totalInterval += timestamps[i] - timestamps[i - 1];
    }
    const avgInterval = totalInterval / (timestamps.length - 1);

    // 预测下次访问
    const lastAccess = timestamps[timestamps.length - 1];
    const predictedAccess = lastAccess + avgInterval;

    this.predictions.set(key, predictedAccess);

    // 在预测访问前预热
    const warmupTime = predictedAccess - avgInterval * 0.1;  // 提前10%
    const delay = warmupTime - Date.now();

    if (delay > 0 && delay < 3600000) {  // 在下一个小时内
      setTimeout(async () => {
        await this.warmKey(key);
      }, delay);
    }
  }

  async warmKey(key) {
    const value = await this.loader(key);
    await cache.set(key, value, { ttl: 3600 });
  }
}

多级缓存(L1/L2)

两级缓存

class TwoLevelCache {
  constructor(l1, l2) {
    this.l1 = l1;  // 快速,小缓存(例如,内存中)
    this.l2 = l2;  // 慢速,大缓存(例如,Redis)
  }

  async get(key) {
    // 首先检查L1
    const l1Value = await this.l1.get(key);
    if (l1Value) {
      return l1Value;
    }

    // 检查L2
    const l2Value = await this.l2.get(key);
    if (l2Value) {
      // 提升到L1
      await this.l1.set(key, l2Value, { ttl: 300 });  // 5分钟
      return l2Value;
    }

    return null;
  }

  async set(key, value, options = {}) {
    // 在两个级别都设置
    await this.l1.set(key, value, { ttl: 300, ...options });
    await this.l2.set(key, value, options);
  }

  async invalidate(key) {
    await this.l1.del(key);
    await this.l2.del(key);
  }
}

// 使用
const l1 = new InMemoryCache({ maxSize: 1000 });
const l2 = new RedisCache();
const cache = new TwoLevelCache(l1, l2);

写入时多级

class WriteThroughMultiTierCache {
  constructor(tiers) {
    this.tiers = tiers;  // [L1, L2, L3, ...]
  }

  async get(key) {
    for (const tier of this.tiers) {
      const value = await tier.get(key);
      if (value) {
        // 提升到更高级别
        this.promote(key, value);
        return value;
      }
    }
    return null;
  }

  async set(key, value, options) {
    // 写入所有级别
    const promises = this.tiers.map(tier =>
      tier.set(key, value, options)
    );
    await Promise.all(promises);
  }

  async invalidate(key) {
    const promises = this.tiers.map(tier => tier.del(key));
    await Promise.all(promises);
  }

  async promote(key, value) {
    // 只写入到更高级别
    for (let i = 0; i < this.tiers.length - 1; i++) {
      await this.tiers[i].set(key, value, { ttl: 300 });
    }
  }
}

CDN缓存失效

CDN清除

const CloudFront = require('aws-sdk/clients/cloudfront');

const cloudfront = new CloudFront({
  region: 'us-east-1',
});

async function invalidateCDN(paths) {
  const params = {
    DistributionId: process.env.CLOUDFRONT_DISTRIBUTION_ID,
    InvalidationBatch: {
      CallerReference: Date.now().toString(),
      Paths: {
        Quantity: paths.length,
        Items: paths,
      },
    },
  };

  const result = await cloudfront.createInvalidation(params).promise();
  return result.Invalidation;
}

// 使用
await invalidateCDN(['/user/123', '/user/123/profile']);

CDN缓存标签

// 设置缓存头
app.get('/user/:id', async (req, res) => {
  const user = await db.users.findById(req.params.id);

  res.set('Cache-Control', 'public, max-age=3600');
  res.set('Cache-Tag', `user-${user.id},premium-${user.isPremium}`);

  res.json(user);
});

// 通过标签失效
async function invalidateByTag(tag) {
  await cloudfront.createInvalidation({
    DistributionId: process.env.CLOUDFRONT_DISTRIBUTION_ID,
    InvalidationBatch: {
      CallerReference: Date.now().toString(),
      Paths: {
        Quantity: 1,
        Items: [`*`],  // 失效所有,按标签过滤
      },
    },
  }).promise();
}

数据库查询缓存失效

基于查询的失效

class QueryCache {
  constructor() {
    this.queryDependencies = new Map();  // 查询 -> 影响的表
  }

  async query(sql, params, loader) {
    const cacheKey = this.getQueryKey(sql, params);

    const cached = await cache.get(cacheKey);
    if (cached) {
      return cached;
    }

    const result = await loader(sql, params);
    await cache.set(cacheKey, result, { ttl: 3600 });

    // 跟踪依赖
    this.trackDependencies(cacheKey, sql);

    return result;
  }

  trackDependencies(cacheKey, sql) {
    const tables = this.extractTables(sql);
    this.queryDependencies.set(cacheKey, tables);
  }

  extractTables(sql) {
    // 简单的表提取
    const matches = sql.match(/FROM\s+(\w+)/gi) || [];
    return matches.map(m => m.replace(/FROM\s+/i, '').toLowerCase());
  }

  async invalidateTable(table) {
    for (const [cacheKey, tables] of this.queryDependencies) {
      if (tables.includes(table)) {
        await cache.del(cacheKey);
      }
    }
  }
}

// 使用
const queryCache = new QueryCache();

async function getUsers() {
  return await queryCache.query(
    'SELECT * FROM users WHERE active = true',
    [],
    (sql, params) => db.query(sql, params)
  );
}

// 当用户表变更时失效
await queryCache.invalidateTable('users');

最终一致性处理

版本向量

class VersionVectorCache {
  constructor() {
    this.versions = new Map();  // key -> { replicaId: version }
  }

  async get(key) {
    const cached = await cache.get(key);
    if (!cached) {
      return null;
    }

    // 检查这个副本的版本是否是最新的
    const myVersion = this.getVersion(key, this.replicaId);
    const cachedVersion = cached._version[this.replicaId];

    if (myVersion > cachedVersion) {
      // 我们的版本更新 - 缓存是过时的
      return null;
    }

    return cached._data;
  }

  async set(key, value) {
    // 增加我们的版本
    this.incrementVersion(key, this.replicaId);

    const version = this.getVersion(key, this.replicaId);

    await cache.set(key, {
      _data: value,
      _version: this.getFullVersion(key),
    });
  }

  getVersion(key, replicaId) {
    const versions = this.versions.get(key) || {};
    return versions[replicaId] || 0;
  }

  incrementVersion(key, replicaId) {
    const versions = this.versions.get(key) || {};
    versions[replicaId] = (versions[replicaId] || 0) + 1;
    this.versions.set(key, versions);
  }
}

监控缓存命中率/未命中率

指标收集

class CacheMetrics {
  constructor() {
    this.hits = 0;
    this.misses = 0;
    this.errors = 0;
  }

  recordHit() {
    this.hits++;
  }

  recordMiss() {
    this.misses++;
  }

  recordError() {
    this.errors++;
  }

  getHitRate() {
    const total = this.hits + this.misses;
    return total > 0 ? this.hits / total : 0;
  }

  getStats() {
    return {
      hits: this.hits,
      misses: this.misses,
      errors: this.errors,
      hitRate: this.getHitRate(),
      total: this.hits + this.misses,
    };
  }

  reset() {
    this.hits = 0;
    this.misses = 0;
    this.errors = 0;
  }
}

// 使用
const metrics = new CacheMetrics();

async function get(key) {
  const cached = await cache.get(key);

  if (cached) {
    metrics.recordHit();
    return cached;
  }

  metrics.recordMiss();
  const value = await loader(key);
  await cache.set(key, value);
  return value;
}

// 定期报告指标
setInterval(() => {
  console.log('缓存统计:', metrics.getStats());
  metrics.reset();
}, 60000);

常见反模式

1. 无锁的缓存旁路

// 坏:多个请求可以加载相同的数据
async function getUser(userId) {
  const cached = await cache.get(`user:${userId}`);
  if (cached) return cached;

  const user = await db.users.findById(userId);  // 多个请求命中数据库
  await cache.set(`user:${userId}`, user);
  return user;
}

// 好:使用锁定或合并请求
async function getUser(userId) {
  return await coalescer.get(`user:${userId}`, async (key) => {
    const id = key.split(':')[1];
    return await db.users.findById(id);
  });
}

2. 不一致的失效

// 坏:更新缓存但未更新数据库
async function updateUser(userId, updates) {
  await cache.set(`user:${userId}`, updates);  // 缓存已更新
  // 忘记更新数据库!
}

// 好:先更新数据库,然后使缓存失效
async function updateUser(userId, updates) {
  const user = await db.users.update(userId, updates);
  await cache.del(`user:${userId}`);
  return user;
}

3. 无TTL

// 坏:无过期,缓存永远不清理
await cache.set('user:123', userData);

// 好:总是设置TTL
await cache.set('user:123', userData, { ttl: 3600 });

4. 缓存一切

// 坏:缓存一切,包括很少访问的数据
async function getData(key) {
  const cached = await cache.get(key);
  if (cached) return cached;

  const value = await db.get(key);
  await cache.set(key, value);
  return value;
}

// 好:根据访问模式有选择地缓存
async function getData(key) {
  if (!shouldCache(key)) {
    return await db.get(key);
  }

  const cached = await cache.get(key);
  if (cached) return cached;

  const value = await db.get(key);
  await cache.set(key, value, { ttl: 3600 });
  return value;
}

最佳实践

  1. 选择正确的策略
    • TTL用于变化缓慢的数据
    • 事件驱动用于关键数据
    • 写入时用于读多写少的工作负载
    • 缓存旁路用于一般用途
  2. 防止缓存踩踏
    • 使用锁定或请求合并
    • 实施后台刷新
    • 考虑对高流量键使用软清除
  3. 优雅地处理失败
    • 始终有数据库作为回退
    • 记录缓存错误
    • 实施断路器
  4. 监控和调整
    • 跟踪命中率/未命中率
    • 监控缓存大小
    • 根据模式调整TTL
  5. 考虑一致性
    • 了解你的一致性需求
    • 使用适当的失效策略
    • 小心地处理分布式场景

相关技能