mcpField
C
caching

caching

github.com/zebbern开发4.7k40 次安装2 天前更新
代码仓库

关于 caching

caching 是面向开发者的缓存设计与代码审查技能,覆盖内存、Redis、CDN 和客户端缓存。它提供失效策略、TTL 与缓存键指导,并列出常见反模式及实现示例。

主要功能

  1. 01制定数据变更时的缓存失效策略
  2. 02按数据变化频率设置 TTL
  3. 03为缓存键纳入相关查询参数
  4. 04选择并组合缓存层
  5. 05识别敏感数据及错误缓存风险
  6. 06参考代码示例配置多种缓存

适用场景

  1. 01为 API 或服务设计缓存层
  2. 02排查过期数据和缓存击穿
  3. 03审查缓存读写代码
  4. 04配置 TanStack Query 的缓存时间

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/zebbern/claude-code-guide --skill caching

安装后在 Claude Code 中输入/caching确认已加载。

详细介绍

适用场景

  • 为 API 或服务实现缓存层(内存、Redis、CDN)时。
  • 为缓存数据选择 TTL 值或失效策略时。
  • 设计缓存键结构以避免冲突或过期数据问题时。
  • 审查读取或写入任何缓存的代码时。
  • 排查数据过期、缓存雪崩或响应不一致问题时。
  • 配置客户端缓存的 TanStack Query staleTime/gcTime 时。

失效策略

  • [P0-MUST] 为每个缓存定义失效策略。过期数据比没有缓存更糟。
  • [P0-MUST] 底层数据发生变化时使缓存失效——不要只依赖 TTL 到期。
  • [P1-SHOULD] 优先采用事件驱动的失效策略(写入/更新/删除时),而不是单纯依赖基于时间的过期机制。
  • [P1-SHOULD] 数据架构发生变化时,使用缓存版本控制(加入版本键)。

TTL 指南

  • [P1-SHOULD] 根据数据变化频率设置 TTL:静态配置(数小时/数天)、用户资料(数分钟)、实时数据(数秒或不缓存)。
  • [P1-SHOULD] 使用过期后后台更新策略:立即提供过期数据,同时在后台刷新。
  • [P2-MAY] 在开发环境中使用较短的 TTL,在生产环境中使用较长的 TTL。

缓存键

  • [P0-MUST] 将所有会影响结果的查询参数纳入缓存键。
  • [P1-SHOULD] 使用一致的键格式:<entity>:<id>:<variant>(例如,user:123:profile、products:list:page=2)。
  • [P1-SHOULD] 按服务或模块对键进行命名空间划分,以防止冲突。
  • [P2-MAY] 对较长或较复杂的键进行哈希处理,以提高存储效率。

缓存层

  • [P1-SHOULD] 根据使用场景选择合适的缓存层:
层级最适合的场景TTL 范围
内存(Map、LRU)热点数据、单实例应用数秒至数分钟
Redis / Memcached多实例共享缓存、会话数分钟至数小时
CDN / Edge静态资源、公开的 API 响应数小时至数天
HTTP 缓存标头浏览器缓存、API 响应因资源而异
  • [P1-SHOULD] 对缓存分层:依次检查内存 → Redis → 源站。未命中时采用直写策略。

不应缓存的情况

  • [P0-MUST] 不要在共享缓存中缓存用户专属的敏感数据(身份验证令牌、支付信息)。
  • [P1-SHOULD] 如果数据快速变化会导致行为错误(库存数量、实时定价),不要缓存这类数据。
  • [P1-SHOULD] 不要缓存错误响应——可设置较短的 TTL,或在失败时跳过缓存。
  • [P2-MAY] 如果计算成本低且数据集较小,可避免使用缓存。

代码示例

带 TTL 的内存 LRU 缓存
const cache = new Map<string, { value: unknown; expires: number }>();
const MAX_SIZE = 500;

export function getOrSet<T>(key: string, ttlMs: number, compute: () => T): T {
  const entry = cache.get(key);
  if (entry && entry.expires > Date.now()) return entry.value as T;

  const value = compute();
  if (cache.size >= MAX_SIZE) {
    // Evict oldest entry (first inserted)
    const oldest = cache.keys().next().value!;
    cache.delete(oldest);
  }
  cache.set(key, { value, expires: Date.now() + ttlMs });
  return value;
}
使用 ioredis 实现过期后后台更新
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);

export async function swr<T>(
  key: string,
  freshSec: number,
  staleSec: number,
  fetcher: () => Promise<T>,
): Promise<T> {
  const raw = await redis.get(key);
  if (raw) {
    const { value, createdAt } = JSON.parse(raw) as { value: T; createdAt: number };
    const ageMs = Date.now() - createdAt;
    if (ageMs < freshSec * 1000) return value; // Fresh — return immediately
    if (ageMs < staleSec * 1000) {
      // Stale — return cached, refresh in background
      fetcher().then((v) =>
        redis.set(key, JSON.stringify({ value: v, createdAt: Date.now() }), "EX", staleSec),
      );
      return value;
    }
  }
  const value = await fetcher();
  await redis.set(key, JSON.stringify({ value, createdAt: Date.now() }), "EX", staleSec);
  return value;
}
Express/Hono 中的 HTTP 缓存标头
// Immutable assets (hashed filenames)
app.use("/assets", (_, res, next) => {
  res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
  next();
});

// API responses — short cache with revalidation
app.get("/api/products", (_, res) => {
  res.setHeader("Cache-Control", "public, max-age=60, stale-while-revalidate=300");
  res.json(products);
});
TanStack Query 缓存配置
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // Data fresh for 5 minutes
      gcTime: 30 * 60 * 1000,   // Garbage-collect after 30 minutes
      retry: 2,
      refetchOnWindowFocus: false,
    },
  },
});

// Usage in a component
const { data } = useQuery({
  queryKey: ["products", { page, category }], // Cache key includes params
  queryFn: () => fetchProducts({ page, category }),
});

反模式

  • 缓存后不管 — 缓存数据却没有失效策略。数据会永久过期。

    • 改为:为每个缓存键定义明确的失效策略(写入时采用事件驱动,或设置有界 TTL)。
  • 统一 TTL — 不考虑数据变化频率,对所有数据使用相同的 TTL(例如,1 小时)。

    • 改为:根据数据变化频率设置 TTL——价格用数秒,用户资料用数分钟,配置用数小时。
  • 键中缺少参数 — 缓存键遗漏用户 ID、区域设置或查询参数,导致提供错误数据。

    • 改为:纳入所有会影响结果的参数:products:list:page=2:locale=en。
  • 缓存错误 — 使用较长的 TTL 存储错误响应(500 错误、超时)。

    • 改为:失败时跳过缓存,或设置极短的 TTL(5-10 秒),以便快速重试。
  • 缓存雪崩 — 热门键过期时,所有实例同时请求源站。

    • 改为:使用过期后后台更新、带随机抖动的 TTL,或互斥锁,让一个实例负责刷新。

评论

支持 Markdown