CachingStrategy caching-strategy

高效缓存策略的实现与优化,包括Redis、Memcached、CDN等技术的应用,旨在提升应用性能和响应速度,减轻数据库负载。

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

缓存策略

概览

实施有效的缓存策略以提高应用程序性能,减少延迟,并减轻后端系统的负载。

何时使用

  • 减少数据库查询负载
  • 提高API响应时间
  • 处理高流量负载
  • 缓存昂贵的计算
  • 存储会话数据
  • CDN集成静态资产
  • 实施分布式缓存
  • 速率限制和节流

缓存层

┌─────────────────────────────────────────┐
│         客户端浏览器缓存            │
├─────────────────────────────────────────┤
│              CDN缓存                  │
├─────────────────────────────────────────┤
│      应用程序内存缓存           │
├─────────────────────────────────────────┤
│     分布式缓存(Redis)          │
├─────────────────────────────────────────┤
│            数据库                     │
└─────────────────────────────────────────┘

实施示例

1. Redis缓存实现(Node.js)

import Redis from 'ioredis';

interface CacheOptions {
  ttl?: number; // 存活时间,以秒为单位
  prefix?: string;
}

class CacheService {
  private redis: Redis;
  private defaultTTL = 3600; // 1小时

  constructor(redisUrl: string) {
    this.redis = new Redis(redisUrl, {
      retryStrategy: (times) => {
        const delay = Math.min(times * 50, 2000);
        return delay;
      },
      maxRetriesPerRequest: 3
    });

    this.redis.on('connect', () => {
      console.log('Redis connected');
    });

    this.redis.on('error', (error) => {
      console.error('Redis error:', error);
    });
  }

  /**
   * 获取缓存值
   */
  async get<T>(key: string): Promise<T | null> {
    try {
      const value = await this.redis.get(key);
      if (!value) return null;

      return JSON.parse(value) as T;
    } catch (error) {
      console.error(`Cache get error for key ${key}:`, error);
      return null;
    }
  }

  /**
   * 设置缓存值
   */
  async set(
    key: string,
    value: any,
    options: CacheOptions = {}
  ): Promise<boolean> {
    try {
      const ttl = options.ttl || this.defaultTTL;
      const serialized = JSON.stringify(value);

      if (ttl > 0) {
        await this.redis.setex(key, ttl, serialized);
      } else {
        await this.redis.set(key, serialized);
      }

      return true;
    } catch (error) {
      console.error(`Cache set error for key ${key}:`, error);
      return false;
    }
  }

  /**
   * 删除缓存值
   */
  async delete(key: string): Promise<boolean> {
    try {
      await this.redis.del(key);
      return true;
    } catch (error) {
      console.error(`Cache delete error for key ${key}:`, error);
      return false;
    }
  }

  /**
   * 按模式删除多个键
   */
  async deletePattern(pattern: string): Promise<number> {
    try {
      const keys = await this.redis.keys(pattern);
      if (keys.length === 0) return 0;

      await this.redis.del(...keys);
      return keys.length;
    } catch (error) {
      console.error(`Cache delete pattern error for ${pattern}:`, error);
      return 0;
    }
  }

  /**
   * 获取或设置模式 - 从缓存中获取或计算并缓存
   */
  async getOrSet<T>(
    key: string,
    fetchFn: () => Promise<T>,
    options: CacheOptions = {}
  ): Promise<T> {
    // 尝试从缓存中获取
    const cached = await this.get<T>(key);
    if (cached !== null) {
      return cached;
    }

    // 获取并缓存
    const value = await fetchFn();
    await this.set(key, value, options);

    return value;
  }

  /**
   * 实现带有失效重验证的缓存旁路模式
   */
  async getStaleWhileRevalidate<T>(
    key: string,
    fetchFn: () => Promise<T>,
    options: {
      ttl: number;
      staleTime: number;
    }
  ): Promise<T> {
    const cacheKey = `cache:${key}`;
    const timestampKey = `cache:${key}:timestamp`;

    const [cached, timestamp] = await Promise.all([
      this.get<T>(cacheKey),
      this.redis.get(timestampKey)
    ]);

    const now = Date.now();
    const age = timestamp ? now - parseInt(timestamp) : Infinity;

    // 如果新鲜则返回缓存
    if (cached !== null && age < options.ttl * 1000) {
      return cached;
    }

    // 如果陈旧则在后台重新验证
    if (cached !== null && age < options.staleTime * 1000) {
      // 后台重新验证
      fetchFn()
        .then(async (fresh) => {
          await this.set(cacheKey, fresh, { ttl: options.ttl });
          await this.redis.set(timestampKey, now.toString());
        })
        .catch(console.error);

      return cached;
    }

    // 获取新鲜数据
    const fresh = await fetchFn();
    await Promise.all([
      this.set(cacheKey, fresh, { ttl: options.ttl }),
      this.redis.set(timestampKey, now.toString())
    ]);

    return fresh;
  }

  /**
   * 带有TTL的计数器增加
   */
  async increment(key: string, ttl?: number): Promise<number> {
    const count = await this.redis.incr(key);

    if (count === 1 && ttl) {
      await this.redis.expire(key, ttl);
    }

    return count;
  }

  /**
   * 检查键是否存在
   */
  async exists(key: string): Promise<boolean> {
    const result = await this.redis.exists(key);
    return result === 1;
  }

  /**
   * 获取剩余TTL
   */
  async ttl(key: string): Promise<number> {
    return await this.redis.ttl(key);
  }

  /**
   * 关闭连接
   */
  async disconnect(): Promise<void> {
    await this.redis.quit();
  }
}

// 使用
const cache = new CacheService('redis://localhost:6379');

// 简单的get/set
await cache.set('user:123', { name: 'John', age: 30 }, { ttl: 3600 });
const user = await cache.get('user:123');

// 获取或设置模式
const posts = await cache.getOrSet(
  'posts:recent',
  async () => {
    return await database.query('SELECT * FROM posts ORDER BY created_at DESC LIMIT 10');
  },
  { ttl: 300 }
);

// 失效重验证
const data = await cache.getStaleWhileRevalidate(
  'expensive-query',
  async () => await runExpensiveQuery(),
  { ttl: 300, staleTime: 600 }
);

2. 缓存装饰器(Python)

import functools
import json
import hashlib
from typing import Any, Callable, Optional
from redis import Redis
import time

class CacheDecorator:
    def __init__(self, redis_client: Redis, ttl: int = 3600):
        self.redis = redis_client
        self.ttl = ttl

    def cache_key(self, func: Callable, *args, **kwargs) -> str:
        """根据函数名称和参数生成缓存键。"""
        # 从函数和参数创建确定性键
        key_parts = [
            func.__module__,
            func.__name__,
            str(args),
            str(sorted(kwargs.items()))
        ]
        key_string = ':'.join(key_parts)
        key_hash = hashlib.md5(key_string.encode()).hexdigest()
        return f"cache:{func.__name__}:{key_hash}"

    def __call__(self, func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 生成缓存键
            cache_key = self.cache_key(func, *args, **kwargs)

            # 尝试从缓存中获取
            cached = self.redis.get(cache_key)
            if cached:
                print(f"Cache HIT: {cache_key}")
                return json.loads(cached)

            # 缓存未命中 - 执行函数
            print(f"Cache MISS: {cache_key}")
            result = func(*args, **kwargs)

            # 存储在缓存中
            self.redis.setex(
                cache_key,
                self.ttl,
                json.dumps(result)
            )

            return result

        # 添加缓存失效方法
        def invalidate(*args, **kwargs):
            cache_key = self.cache_key(func, *args, **kwargs)
            self.redis.delete(cache_key)

        wrapper.invalidate = invalidate
        return wrapper


# 使用
redis = Redis(host='localhost', port=6379, db=0)
cache = CacheDecorator(redis, ttl=300)

@cache
def get_user_profile(user_id: int) -> dict:
    """从数据库获取用户资料。"""
    print(f"Fetching user {user_id} from database...")
    # 模拟数据库查询
    time.sleep(1)
    return {
        'id': user_id,
        'name': 'John Doe',
        'email': 'john@example.com'
    }

# 第一次调用 - 缓存未命中
profile = get_user_profile(123)  # 需要1秒钟

# 第二次调用 - 缓存命中
profile = get_user_profile(123)  # 立即

# 使缓存失效
get_user_profile.invalidate(123)

3. 多级缓存

interface CacheLevel {
  get(key: string): Promise<any>;
  set(key: string, value: any, ttl?: number): Promise<void>;
  delete(key: string): Promise<void>;
}

class MemoryCache implements CacheLevel {
  private cache = new Map<string, { value: any; expiry: number }>();

  async get(key: string): Promise<any> {
    const item = this.cache.get(key);
    if (!item) return null;

    if (Date.now() > item.expiry) {
      this.cache.delete(key);
      return null;
    }

    return item.value;
  }

  async set(key: string, value: any, ttl: number = 60): Promise<void> {
    this.cache.set(key, {
      value,
      expiry: Date.now() + ttl * 1000
    });
  }

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

  clear(): void {
    this.cache.clear();
  }
}

class RedisCache implements CacheLevel {
  constructor(private redis: Redis) {}

  async get(key: string): Promise<any> {
    const value = await this.redis.get(key);
    return value ? JSON.parse(value) : null;
  }

  async set(key: string, value: any, ttl: number = 3600): Promise<void> {
    await this.redis.setex(key, ttl, JSON.stringify(value));
  }

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

class MultiLevelCache {
  private levels: CacheLevel[];

  constructor(levels: CacheLevel[]) {
    this.levels = levels; // 从最快到最慢排序
  }

  async get<T>(key: string): Promise<T | null> {
    for (let i = 0; i < this.levels.length; i++) {
      const value = await this.levels[i].get(key);

      if (value !== null) {
        // 回填更快的缓存
        for (let j = 0; j < i; j++) {
          await this.levels[j].set(key, value);
        }

        return value as T;
      }
    }

    return null;
  }

  async set(key: string, value: any, ttl?: number): Promise<void> {
    // 在所有缓存层中设置
    await Promise.all(
      this.levels.map(level => level.set(key, value, ttl))
    );
  }

  async delete(key: string): Promise<void> {
    await Promise.all(
      this.levels.map(level => level.delete(key))
    );
  }
}

// 使用
const cache = new MultiLevelCache([
  new MemoryCache(),
  new RedisCache(redis)
]);

// 从最快的可用缓存中获取
const data = await cache.get('user:123');

// 在所有缓存中设置
await cache.set('user:123', userData, 3600);

4. 缓存失效策略

class CacheInvalidation {
  constructor(private cache: CacheService) {}

  /**
   * 基于时间的失效(TTL)
   */
  async setWithTTL(key: string, value: any, seconds: number): Promise<void> {
    await this.cache.set(key, value, { ttl: seconds });
  }

  /**
   * 基于标签的失效
   */
  async setWithTags(
    key: string,
    value: any,
    tags: string[]
  ): Promise<void> {
    // 存储值
    await this.cache.set(key, value);

    // 存储标签关联
    for (const tag of tags) {
      await this.cache.redis.sadd(`tag:${tag}`, key);
    }
  }

  async invalidateByTag(tag: string): Promise<number> {
    // 获取所有具有此标签的键
    const keys = await this.cache.redis.smembers(`tag:${tag}`);

    if (keys.length === 0) return 0;

    // 删除所有键
    await Promise.all(
      keys.map(key => this.cache.delete(key))
    );

    // 删除标签集合
    await this.cache.redis.del(`tag:${tag}`);

    return keys.length;
  }

  /**
   * 基于事件的失效
   */
  async invalidateOnEvent(
    entity: string,
    id: string,
    event: 'create' | 'update' | 'delete'
  ): Promise<void> {
    const patterns = [
      `${entity}:${id}`,
      `${entity}:${id}:*`,
      `${entity}:list:*`,
      `${entity}:count`
    ];

    for (const pattern of patterns) {
      await this.cache.deletePattern(pattern);
    }
  }

  /**
   * 基于版本的失效
   */
  async setVersioned(
    key: string,
    value: any,
    version: number
  ): Promise<void> {
    const versionedKey = `${key}:v${version}`;
    await this.cache.set(versionedKey, value);
    await this.cache.set(`${key}:version`, version);
  }

  async getVersioned(key: string): Promise<any> {
    const version = await this.cache.get<number>(`${key}:version`);
    if (!version) return null;

    return await this.cache.get(`${key}:v${version}`);
  }
}

5. HTTP缓存头

import express from 'express';

const app = express();

// Cache-Control中间件
function cacheControl(maxAge: number, options: {
  private?: boolean;
  noStore?: boolean;
  noCache?: boolean;
  mustRevalidate?: boolean;
  staleWhileRevalidate?: number;
} = {}) {
  return (req: express.Request, res: express.Response, next: express.NextFunction) => {
    const directives: string[] = [];

    if (options.noStore) {
      directives.push('no-store');
    } else if (options.noCache) {
      directives.push('no-cache');
    } else {
      directives.push(options.private ? 'private' : 'public');
      directives.push(`max-age=${maxAge}`);

      if (options.staleWhileRevalidate) {
        directives.push(`stale-while-revalidate=${options.staleWhileRevalidate}`);
      }
    }

    if (options.mustRevalidate) {
      directives.push('must-revalidate');
    }

    res.setHeader('Cache-Control', directives.join(', '));
    next();
  };
}

// 静态资产 - 长缓存
app.use('/static', cacheControl(31536000), express.static('public'));

// API - 短缓存与重验证
app.get('/api/data',
  cacheControl(60, { staleWhileRevalidate: 300 }),
  (req, res) => {
    res.json({ data: 'cached for 60s' });
  }
);

// 动态内容 - 无缓存
app.get('/api/user/profile',
  cacheControl(0, { private: true, noCache: true }),
  (req, res) => {
    res.json({ user: 'always fresh' });
  }
);

// ETag支持
app.get('/api/resource/:id', async (req, res) => {
  const resource = await getResource(req.params.id);
  const etag = generateETag(resource);

  res.setHeader('ETag', etag);

  // 检查客户端是否有当前版本
  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end();
  }

  res.json(resource);
});

function generateETag(data: any): string {
  return require('crypto')
    .createHash('md5')
    .update(JSON.stringify(data))
    .digest('hex');
}

最佳实践

✅ 执行

  • 设置适当的TTL值
  • 为关键数据实施缓存预热
  • 对读取使用缓存旁路模式
  • 监控缓存命中率
  • 在缓存失败时实现优雅降级
  • 对大的缓存值使用压缩
  • 适当命名空间缓存键
  • 实施缓存踩踏预防
  • 对分布式缓存使用一致性哈希
  • 监控缓存内存使用情况

❌ 不执行

  • 无差别地缓存一切
  • 使用缓存作为数据库设计不佳的解决方案
  • 未经加密存储敏感数据
  • 忘记处理缓存未命中
  • 为经常变化的数据设置过长的TTL
  • 忽略缓存失效策略
  • 不监控缓存
  • 无考虑地存储大对象

缓存策略

策略 描述 用例
旁路缓存 应用程序检查缓存,未命中时从数据库加载 通用目的
写入旁路 同时写入缓存和数据库 需要强一致性
写入后 写入缓存,异步写入数据库 高写入吞吐量
提前刷新 在到期前主动刷新 可预测的访问模式
读取旁路 缓存自动从数据库加载 简化代码

资源