chore: import zh skill caching
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- Skill 名称:`caching`
|
||||
- 中文类目:缓存策略
|
||||
- 上游仓库:`zebbern__claude-code-guide`
|
||||
- 上游路径:`skills/caching/SKILL.md`
|
||||
- 上游链接:https://github.com/zebbern/claude-code-guide/blob/HEAD/skills/caching/SKILL.md
|
||||
- 本仓库为 WeHub 中文 Skill 汉化包,基于 skill 市场筛选 Top200 清单整理
|
||||
- 原作者、版权和许可证信息以上游仓库为准
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
name: caching
|
||||
description: 缓存策略——失效策略、TTL 指导原则、缓存键、缓存层级,以及何时不应使用缓存。适用于实现或审查缓存逻辑时使用。
|
||||
---
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 在为实现 API 或服务添加缓存层(内存缓存、Redis、CDN)时。
|
||||
- 在为缓存数据选择 TTL 值或失效策略时。
|
||||
- 在设计缓存键模式以避免冲突或脏数据 bug 时。
|
||||
- 在审查从缓存读取或写入缓存的代码时。
|
||||
- 在调试脏数据、缓存雪崩或不一致响应时。
|
||||
- 在配置 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] 使用统一的键格式:`<实体>:<ID>:<变体>`(例如 `user:123:profile`、`products:list:page=2`)。
|
||||
- [P1-SHOULD] 按服务或模块对键进行命名空间划分,防止冲突。
|
||||
- [P2-MAY] 对较长或较复杂的键进行哈希处理,以保持存储效率。
|
||||
|
||||
## 缓存层级
|
||||
|
||||
- [P1-SHOULD] 根据使用场景选择合适的缓存层级:
|
||||
|
||||
| 层级 | 最适合的场景 | TTL 范围 |
|
||||
|-------|----------|-----------|
|
||||
| 内存缓存(Map、LRU) | 热点数据、单实例应用 | 秒到分钟 |
|
||||
| Redis / Memcached | 跨实例的共享缓存、会话 | 分钟到小时 |
|
||||
| CDN / 边缘节点 | 静态资源、公开 API 响应 | 小时到天 |
|
||||
| HTTP 缓存头 | 浏览器缓存、API 响应 | 因资源而异 |
|
||||
|
||||
- [P1-SHOULD] 分层使用缓存:检查内存 → Redis → 源服务器。未命中时回填写入。
|
||||
|
||||
## 何时不应使用缓存
|
||||
|
||||
- [P0-MUST] 不得在共享缓存中缓存用户特定的敏感数据(身份令牌、支付信息)。
|
||||
- [P1-SHOULD] 不应缓存变化频繁且过时数据会导致错误行为的数据(库存数量、实时定价)。
|
||||
- [P1-SHOULD] 不应缓存错误响应——使用较短的 TTL,或在失败时跳过缓存。
|
||||
- [P2-MAY] 当计算开销小且数据量不大时,应避免使用缓存。
|
||||
|
||||
## 代码示例
|
||||
|
||||
### 带 TTL 的内存 LRU 缓存
|
||||
|
||||
```ts
|
||||
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) {
|
||||
// 淘汰最旧的条目(最先插入的)
|
||||
const oldest = cache.keys().next().value!;
|
||||
cache.delete(oldest);
|
||||
}
|
||||
cache.set(key, { value, expires: Date.now() + ttlMs });
|
||||
return value;
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 ioredis 的陈旧时重新验证模式
|
||||
|
||||
```ts
|
||||
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; // 新鲜——直接返回
|
||||
if (ageMs < staleSec * 1000) {
|
||||
// 陈旧——返回缓存值,后台刷新
|
||||
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 缓存头
|
||||
|
||||
```ts
|
||||
// 不可变资源(已哈希文件名)
|
||||
app.use("/assets", (_, res, next) => {
|
||||
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
||||
next();
|
||||
});
|
||||
|
||||
// API 响应——短缓存 + 重新验证
|
||||
app.get("/api/products", (_, res) => {
|
||||
res.setHeader("Cache-Control", "public, max-age=60, stale-while-revalidate=300");
|
||||
res.json(products);
|
||||
});
|
||||
```
|
||||
|
||||
### TanStack Query 缓存配置
|
||||
|
||||
```tsx
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 5 * 60 * 1000, // 数据在 5 分钟内视为新鲜
|
||||
gcTime: 30 * 60 * 1000, // 30 分钟后进行垃圾回收
|
||||
retry: 2,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 在组件中使用
|
||||
const { data } = useQuery({
|
||||
queryKey: ["products", { page, category }], // 缓存键包含参数
|
||||
queryFn: () => fetchProducts({ page, category }),
|
||||
});
|
||||
```
|
||||
|
||||
## 反模式
|
||||
|
||||
- **缓存后不闻不问**——缓存数据但没有失效策略。数据永久变脏。
|
||||
- 替代做法:为每个缓存键定义显式的失效策略(写入时事件驱动,或设定有边界的 TTL)。
|
||||
|
||||
- **统一 TTL**——无论数据变化频率如何,对所有数据使用相同的 TTL(例如 1 小时)。
|
||||
- 替代做法:使 TTL 匹配数据变化频率——价格用秒级,用户资料用分钟级,配置用小时级。
|
||||
|
||||
- **缺少关键参数**——缓存键缺少用户 ID、语言环境或查询参数,导致返回错误数据。
|
||||
- 替代做法:包含所有影响结果的条件参数:`products:list:page=2:locale=en`。
|
||||
|
||||
- **缓存错误响应**——将错误响应(500 错误、超时)以较长 TTL 存储。
|
||||
- 替代做法:失败时跳过缓存,或使用极短的 TTL(5-10 秒)以便快速重试。
|
||||
|
||||
- **缓存雪崩**——当某个热门键过期时,所有实例同时请求源服务器。
|
||||
- 替代做法:使用陈旧时重新验证、带有抖动的 TTL,或通过互斥锁让单个实例刷新。
|
||||
Reference in New Issue
Block a user