正在加载,请稍候…

后端应用缓存策略:Redis 模式

使用 Redis 实现高效的缓存策略。学习缓存旁路、直写、通读模式,缓存失效和分布式缓存。

后端应用缓存策略:Redis 模式

后端应用缓存策略

缓存旁路(惰性加载)

应用程序手动管理缓存。最常见的模式。

class UserService {
  constructor(
    private userRepo: UserRepository,
    private cache: RedisCache
  ) {}

  async getUser(userId: string): Promise<User> {
    const cacheKey = `user:${userId}`;

    // 1. 先检查缓存
    const cached = await this.cache.get<User>(cacheKey);
    if (cached) return cached;

    // 2. 缓存未命中 - 从数据库获取
    const user = await this.userRepo.findById(userId);
    if (!user) throw new UserNotFoundError(userId);

    // 3. 存入缓存并设置 TTL
    await this.cache.set(cacheKey, user, { ttl: 3600 }); // 1 小时

    return user;
  }

  async updateUser(userId: string, dto: UpdateUserDto): Promise<User> {
    const user = await this.userRepo.update(userId, dto);

    // 更新后使缓存失效
    await this.cache.delete(`user:${userId}`);

    return user;
  }
}

后端应用缓存策略:Redis 模式示意图

直写缓存

同时写入缓存和数据库。

async setUser(user: User): Promise<void> {
  const cacheKey = `user:${user.id}`;

  // 同时写入两者
  await Promise.all([
    this.userRepo.save(user),
    this.cache.set(cacheKey, user, { ttl: 3600 }),
  ]);
}

后端应用缓存策略:Redis 模式示意图

Redis 缓存实现

import { createClient } from 'redis';

class RedisCache {
  private client = createClient({ url: process.env.REDIS_URL });

  async get<T>(key: string): Promise<T | null> {
    const data = await this.client.get(key);
    if (!data) return null;
    return JSON.parse(data) as T;
  }

  async set<T>(key: string, value: T, options: { ttl?: number } = {}): Promise<void> {
    const serialized = JSON.stringify(value);
    if (options.ttl) {
      await this.client.setEx(key, options.ttl, serialized);
    } else {
      await this.client.set(key, serialized);
    }
  }

  async delete(key: string): Promise<void> {
    await this.client.del(key);
  }

  async deletePattern(pattern: string): Promise<void> {
    const keys = await this.client.keys(pattern);
    if (keys.length > 0) {
      await this.client.del(keys);
    }
  }

  async getOrSet<T>(key: string, factory: () => Promise<T>, ttl = 3600): Promise<T> {
    const cached = await this.get<T>(key);
    if (cached !== null) return cached;
    const value = await factory();
    await this.set(key, value, { ttl });
    return value;
  }
}

后端应用缓存策略:Redis 模式示意图

缓存失效策略

// 基于标签的失效
class TaggedCache {
  async setWithTags(key: string, value: unknown, tags: string[], ttl = 3600): Promise<void> {
    await this.cache.set(key, value, { ttl });
    // 存储反向映射:tag -> keys
    for (const tag of tags) {
      await this.client.sAdd(`tag:${tag}`, key);
      await this.client.expire(`tag:${tag}`, ttl);
    }
  }

  async invalidateByTag(tag: string): Promise<void> {
    const keys = await this.client.sMembers(`tag:${tag}`);
    if (keys.length > 0) {
      await this.client.del([...keys, `tag:${tag}`]);
    }
  }
}

// 当用户更新时,使相关缓存失效
await taggedCache.setWithTags(
  `user:${userId}`,
  user,
  [`user:${userId}`, 'users:list'],
  3600
);

await taggedCache.invalidateByTag(`user:${userId}`);

缓存雪崩预防

// 互斥锁防止多个同时重建
async getWithLock<T>(key: string, factory: () => Promise<T>): Promise<T> {
  const cached = await this.cache.get<T>(key);
  if (cached !== null) return cached;

  const lockKey = `lock:${key}`;
  const lockAcquired = await this.client.set(lockKey, '1', { NX: true, EX: 5 });

  if (!lockAcquired) {
    // 另一个进程正在重建,等待并重试
    await new Promise(resolve => setTimeout(resolve, 100));
    return this.getWithLock(key, factory);
  }

  try {
    const value = await factory();
    await this.cache.set(key, value, { ttl: 3600 });
    return value;
  } finally {
    await this.client.del(lockKey);
  }
}

缓存命中率监控

class MonitoredCache extends RedisCache {
  private hits = 0;
  private misses = 0;

  async get<T>(key: string): Promise<T | null> {
    const result = await super.get<T>(key);
    if (result !== null) {
      this.hits++;
      metrics.increment('cache.hit', { key: key.split(':')[0] });
    } else {
      this.misses++;
      metrics.increment('cache.miss', { key: key.split(':')[0] });
    }
    return result;
  }

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

对于频繁访问的数据,目标缓存命中率达到 95% 以上。监控并根据需要调整 TTL。