关于 caching
caching 是面向开发者的缓存设计与代码审查技能,覆盖内存、Redis、CDN 和客户端缓存。它提供失效策略、TTL 与缓存键指导,并列出常见反模式及实现示例。
主要功能
- 01制定数据变更时的缓存失效策略
- 02按数据变化频率设置 TTL
- 03为缓存键纳入相关查询参数
- 04选择并组合缓存层
- 05识别敏感数据及错误缓存风险
- 06参考代码示例配置多种缓存
适用场景
- 01为 API 或服务设计缓存层
- 02排查过期数据和缓存击穿
- 03审查缓存读写代码
- 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