一键导入
caching-strategy
设计和实现缓存策略时使用。覆盖 Redis / Memcached / Caffeine(Java)/ NestJS Cache / Spring Cache / Django Cache。融合 5 种缓存模式 + 三大缓存问题 + 失效策略。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
设计和实现缓存策略时使用。覆盖 Redis / Memcached / Caffeine(Java)/ NestJS Cache / Spring Cache / Django Cache。融合 5 种缓存模式 + 三大缓存问题 + 失效策略。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
设计 API 认证鉴权和权限矩阵时使用。适用于多角色系统、租户隔离、字段级权限。优先使用 OAuth 2.0 / JWT + RBAC + 资源归属检查。
设计具体 API 端点时使用。适用于资源建模后的下一步、列端点清单、HTTP 方法和状态码选择。优先使用 RFC 7231 HTTP 语义 + GitHub REST 命名规范。
设计 API 错误码和错误结构时使用。适用于错误响应规范、调用方错误处理、调试可观测。优先使用 RFC 7807 Problem Details + 业务错误码 + 调用方处理建议。
设计幂等接口和重试策略时使用。适用于支付、扣减、订单、关键写操作。优先使用 Idempotency-Key + 业务去重键 + 并发冲突处理(ETag/版本号)。
输出 OpenAPI 契约和 Mock 服务时使用。适用于 API 设计的最后一步、给前端/后端/QA 的交付。优先使用 OpenAPI 3.1 + Mock 数据覆盖所有路径 + 详细的下游交接清单。
设计列表接口的分页、筛选、排序、搜索时使用。适用于所有列表 API。优先使用 cursor 分页(大数据)或 offset 分页(小数据)+ 统一筛选/排序规范。
| name | caching-strategy |
| description | 设计和实现缓存策略时使用。覆盖 Redis / Memcached / Caffeine(Java)/ NestJS Cache / Spring Cache / Django Cache。融合 5 种缓存模式 + 三大缓存问题 + 失效策略。 |
参考来源:Martin Fowler《Patterns of Enterprise Application Architecture》Cache、Stripe / Netflix / Twitter 缓存实践、Redis 官方最佳实践、Spring Cache / NestJS Cache 文档。
1. 不为过早优化引入缓存
先确认 DB 真的是瓶颈
2. 缓存必须可失效 / 可清除
有缓存就有不一致
3. 缓存 Key 设计要稳定 + 唯一
不要用查询参数随便拼
4. TTL 必须设置
永不过期 = 内存泄漏
5. 接受最终一致性
"实时" 和 "缓存" 二选一
6. 防三大问题:穿透 / 击穿 / 雪崩
每个高并发缓存都要考虑
7. 缓存 hit 率 > 80% 才有意义
< 50% 增加复杂度无收益
应用控制:
读:先查缓存,未命中查 DB,回填缓存
写:写 DB,失效缓存
优点:简单、可控
缺点:首次请求慢
// Spring 范式
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id")
public Product getById(Long id) {
return productRepo.findById(id).orElse(null);
}
@CacheEvict(value = "products", key = "#product.id")
public void update(Product product) {
productRepo.save(product);
}
@CacheEvict(value = "products", allEntries = true)
public void clearAll() { /* 批量失效 */ }
}
// NestJS 范式
@Injectable()
export class ProductService {
constructor(
@Inject(CACHE_MANAGER) private cache: Cache,
private productRepo: ProductRepository,
) {}
async getById(id: number): Promise<Product> {
const cacheKey = `product:${id}`;
const cached = await this.cache.get<Product>(cacheKey);
if (cached) return cached;
const product = await this.productRepo.findById(id);
if (product) {
await this.cache.set(cacheKey, product, 600); // 10 min TTL
}
return product;
}
async update(product: Product): Promise<Product> {
const updated = await this.productRepo.update(product);
await this.cache.del(`product:${product.id}`);
return updated;
}
}
# Django
from django.core.cache import cache
def get_product(product_id):
cache_key = f'product:{product_id}'
product = cache.get(cache_key)
if product is None:
product = Product.objects.get(id=product_id)
cache.set(cache_key, product, 600) # 10 min
return product
def update_product(product):
product.save()
cache.delete(f'product:{product.id}')
缓存层负责加载:
读:先查缓存,未命中由缓存层加载
特点:应用代码不知道是否未命中
写穿:
写:同时写缓存和 DB,都成功才返回
优点:缓存与 DB 强一致
缺点:写慢一倍
写后异步:
写:先写缓存,异步刷 DB
优点:写极快
缺点:DB 故障可能丢数据
预测刷新:
快过期前主动刷新(避免缓存击穿)
适合:热点数据 + 读非常多
症状:查询不存在的数据,每次都打 DB
攻击:恶意构造大量不存在的 ID 查询
解决:
方案 A:缓存空值(短 TTL)
cache.set('user:999999', null, 60);
方案 B:布隆过滤器(Bloom Filter)
if (!bloomFilter.mightContain(id)) return null;
// Spring + 缓存空值
@Cacheable(value = "products", key = "#id", unless = "#result == null")
// unless 控制不缓存 null
// 但要主动缓存"已知不存在"
public Product getById(Long id) {
Product product = productRepo.findById(id).orElse(null);
if (product == null) {
cache.put("products::" + id, NULL_PLACEHOLDER, Duration.ofMinutes(1));
}
return product;
}
症状:热点 key 过期瞬间,大量请求穿透到 DB
影响:DB 瞬间压力 100x
解决:
方案 A:互斥锁(Mutex Lock)
只让 1 个请求重建缓存
方案 B:永不过期 + 异步刷新
缓存自身不过期,定时后台刷新
方案 C:Refresh-Ahead
临近过期时主动刷新
// 互斥锁示例(Redis)
async function getWithMutex(key: string): Promise<any> {
let value = await redis.get(key);
if (value) return value;
// 尝试拿锁(30 秒超时)
const lock = await redis.set(`lock:${key}`, '1', 'PX', 30000, 'NX');
if (!lock) {
// 其他人在重建,等一下再读
await sleep(50);
return getWithMutex(key);
}
try {
value = await loadFromDB(key);
await redis.set(key, value, 'EX', 600);
return value;
} finally {
await redis.del(`lock:${key}`);
}
}
症状:大量 key 同时过期 / 缓存服务挂了
影响:DB 瞬间压力爆炸
解决:
方案 A:TTL 加随机抖动
expire = base + random(0, 300) // 防同时过期
方案 B:多级缓存(Caffeine + Redis)
L1 进程内 + L2 分布式
方案 C:限流 + 熔断
DB 压力大时降级返回兜底数据
方案 D:缓存预热
上线前手动 warm up 关键 key
✅ 推荐:
product:123 # 简单实体
user:123:profile # 实体 + 字段
list:orders:user:123:status:paid:p1 # 列表查询
count:active_users:tenant:5 # 计数器
❌ 反例:
user_123_profile (用 _ 容易冲突)
user-123-profile (-/_ 不一致)
$user:123:profile (特殊字符问题)
USER:123 (大小写混用)
1. 用 ":" 分隔层次(Redis 习惯)
2. 小写
3. 业务前缀 + 实体类型 + ID + 字段
4. 列表查询带过滤参数
5. 长度 < 128 字节
不同数据 TTL 不同:
会话 / Token: 30 分钟 ~ 24 小时
配置数据: 1 小时 ~ 1 天
实体详情: 10 分钟 ~ 1 小时
列表查询: 1 ~ 10 分钟
计数器: 短期(秒级)
排行榜: 5 ~ 60 分钟
统计聚合: 1 ~ 24 小时
防雪崩:
TTL = base + jitter
base = 600 秒
jitter = random(0, 300) 秒
// 数据更新时同步失效
async function updateProduct(product: Product) {
await productRepo.update(product);
await cache.del([
`product:${product.id}`,
`product:list:*`, // 通配符失效(慎用)
]);
}
await cache.set(key, value, 'EX', 600); // 10 min 后过期
// 不删除缓存,改版本号
const version = await cache.incr(`product:list:version`);
// 读
async function getList() {
const version = await cache.get('product:list:version');
return cache.get(`product:list:${version}`);
}
// 写
async function update() {
await productRepo.update();
await cache.incr('product:list:version'); // 版本号 +1
}
// 缓存时打标签
await cache.set('product:1', data, { tags: ['products', 'category:electronics'] });
// 失效某标签下全部
await cache.invalidateTag('category:electronics');
L1:进程内(Caffeine / Map / LRU)
- 极低延迟(< 1μs)
- 容量小
- 实例间不一致
L2:分布式(Redis)
- 实例间一致
- 网络延迟(~ 1ms)
读流程:
L1 命中 → 返回
L1 未命中 → 查 L2
L2 命中 → 写 L1,返回
L2 未命中 → 查 DB,写 L1 + L2
// Spring + Caffeine + Redis 多级
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CompositeCacheManager composite = new CompositeCacheManager(
caffeineCacheManager(), // L1
redisCacheManager() // L2
);
composite.setFallbackToNoOpCache(true);
return composite;
}
}
// 单实例锁(SET NX)
async function acquireLock(key: string, ttl: number): Promise<boolean> {
const result = await redis.set(`lock:${key}`, '1', 'PX', ttl, 'NX');
return result === 'OK';
}
// Redlock(多实例):用 Redlock 库
import Redlock from 'redlock';
const redlock = new Redlock([redis1, redis2, redis3]);
const lock = await redlock.acquire(['resource'], 5000);
try {
// 临界区
} finally {
await lock.release();
}
// 滑动窗口限流
async function checkRateLimit(userId: string, limit: number, windowMs: number): Promise<boolean> {
const key = `ratelimit:${userId}`;
const now = Date.now();
// 移除过期记录
await redis.zremrangebyscore(key, 0, now - windowMs);
// 计数
const count = await redis.zcard(key);
if (count >= limit) return false;
// 加入新记录
await redis.zadd(key, now, `${now}-${randomUUID()}`);
await redis.expire(key, Math.ceil(windowMs / 1000));
return true;
}
1. 评估是否需要缓存
- DB 真的是瓶颈吗?
- 数据更新频率?
- 一致性要求?
2. 选择缓存模式
- Cache-Aside(默认)
- Write-Through(强一致)
- Read-Through(封装好)
3. 设计 Key
- 命名规范
- 长度合理
- 唯一可识别
4. 设计 TTL
- 业务可接受的不一致时长
- 加随机抖动
5. 设计失效
- 写时主动清理
- 或:版本号
- 或:标签
6. 防三大问题
- 穿透:缓存空值 / 布隆
- 击穿:互斥锁 / 永不过期
- 雪崩:TTL 抖动 / 多级 / 限流
7. 监控
- hit rate
- 内存使用
- 慢查询
- 连接池
8. 验证
- 压测对比 with/without 缓存
- hit rate > 80%
templates/cache-strategy-template.md — 缓存方案 + Key 命名 + TTL + 失效 + 防三大问题 + 监控□ 缓存有明确收益(DB 是瓶颈)
□ Key 命名规范(业务 : 实体 : ID)
□ TTL 设置(不能永不过期)
□ TTL 加随机抖动防雪崩
□ 写时同步失效
□ 防穿透:缓存空值或布隆
□ 防击穿:互斥锁或永不过期
□ 防雪崩:多级缓存或限流
□ hit rate 监控
□ 内存使用监控
□ 不缓存 PII / 敏感数据
□ 用结构化命名(前缀分隔)
□ 大对象考虑序列化效率
□ 不滥用通配符 KEYS(生产禁用)
上游:
data-access → 数据访问 + 缓存层
observability → 缓存监控
下游:
error-handling-resilience → 缓存挂了的降级
async-jobs → 异步刷新缓存
references/backend-tech-stack-guide.md — 缓存方案对比