缓存策略

指导 Agent 按层级(浏览器、CDN、应用、分布式缓存)给出键空间、TTL 与主动/被动失效组合,并显式标注一致性代价;本页含读路径总览与键名草稿工具。

先识别数据新鲜度:强一致路径避免跨层缓存或缩短 TTL 并配合版本号;最终一致可接受 stale-while-revalidate、异步刷新与单飞回填。

SKILL 须写出「失效触发点」:写库成功后删哪些键、是否延迟双删、消息乱序或延迟时何时强制读主库或拒绝陈旧版本。

  • 键设计包含租户、资源类型、语言/区域、schema 版本等维度,避免隐式全局键。
  • 热点键考虑分片、本地二级缓冲与限流;与限流、熔断策略在文档中交叉引用。

一页摘要

  • 分层:浏览器 → CDN → 应用内存 / 进程 → Redis 等 → DB;每层声明是否允许缓存与最大陈旧度。
  • 键:可预测的命名空间 + 版本段;写放大与键数量上限在 SKILL 中写明。
  • 失效:写路径维护删除列表或版本 bump;必要时延迟双删与兜底读主。
  • 风险:穿透(空值/布隆)、击穿(互斥/单飞)、雪崩(TTL 抖动 + 预热 + 降级)。
  • 度量:命中率、回源率、延迟分位与「缓存未命中风暴」告警。
# Cache-Aside 模式(Python + Redis)
import json, time, random
import redis

r = redis.Redis(host="localhost", port=6379, decode_responses=True)

def get_product(product_id: int) -> dict:
    key = f"v2:product:{product_id}"
    cached = r.get(key)
    if cached:
        return json.loads(cached)           # Cache hit

    product = db_get_product(product_id)   # Cache miss → 回源 DB
    if product:
        # TTL 基础 300s + 随机抖动 ±30s,防雪崩
        ttl = 300 + random.randint(-30, 30)
        r.setex(key, ttl, json.dumps(product))
    else:
        # 空值缓存防穿透,TTL 短(30s)
        r.setex(key, 30, json.dumps(None))
    return product

# Redis 5 种数据结构及典型缓存用途
# String:  JSON 序列化对象(单实体缓存)r.setex("user:1", 300, json_str)
# Hash:    字段级更新(计数器、配置)     r.hset("session:abc", "uid", 42)
# List:    最近 N 条记录、消息队列        r.lpush("recent:views", prod_id); r.ltrim("recent:views", 0, 99)
# Set:     去重标签、黑名单               r.sadd("blacklist:ip", "1.2.3.4")
# Sorted Set: 排行榜、带分数的优先级队列  r.zadd("leaderboard", {uid: score})

分层读路径(skill-flow-block)

  [ 请求进入:携带 Accept-Language / 租户上下文 ]
                    │
        ┌───────────┴───────────┐
        ▼                       ▼
  [ 浏览器私有缓存 ]         [ CDN / 边缘 ]
  Cache-Control 私有策略     SWR、stale-if-error(若适用)
        │                       │
        └───────────┬───────────┘
                    ▼
         [ 应用内:进程 LRU / 本地 map ]
         注意:多实例一致性,宜短 TTL 或事件失效
                    │
                    ▼
         [ 分布式:Redis / Memcached 等 ]
         单飞回填、管道批量、序列化格式约定
                    │
           ┌────────┴────────┐
           ▼                 ▼
  [ 命中:返回 + 逐层回填(按需)]   [ 未命中:回源 DB / 服务 ]
           │                 │
           └────────┬────────┘
                    ▼
         [ 可选:异步写回各层,记录陈旧度元数据 ]

Agent 输出应标明「哪一层可省略」:例如强一致账户余额通常不进 CDN;只读热点列表可 CDN 长 TTL + 应用短 TTL 组合。

各层职责与缓存头

浏览器 / CDN

  • Cache-Controlprivate / publicmax-ages-maxage
  • ETag / Last-Modified 与条件请求减少带宽。
  • 需登录或含 PII 的响应默认 private,避免共享缓存泄漏。

应用 / 分布式

  • 序列化:JSON、MessagePack、压缩;大 value 分块或旁路对象存储。
  • 连接与超时:避免缓存层拖垮主链路;熔断后行为写清。
  • 多租户:键前缀隔离,防止 SCAN 式调试在生产误用。

键空间、TTL 与抖动

推荐形态示例:{tenant}:{schemaVer}:{entity}:{id}{svc}:{cacheVer}:{自然键哈希};在 SKILL 中禁止依赖「隐式默认租户」。

  • TTL:按业务 SLA 分档;与下游刷新任务频率对齐,避免永久陈旧。
  • 抖动:在基准 TTL 上增加随机偏移(如 ±10%),降低同一时刻集体过期。
  • 负缓存:对合法但不存在的结果使用较短 TTL 的占位值,减轻穿透压力(注意与权限边界)。

写路径与失效清单

写库成功后同步或异步删除相关键;若采用「先删再写」,需评估并发读导致的长窗口不一致,必要时配合版本号或延迟双删。

  • 维护「实体 → 影响键集合」表或代码生成清单,避免遗漏关联列表缓存。
  • 消息总线延迟时:定义超时后读主、或返回带 X-Cache-Stale 元信息的显式陈旧响应(与产品约定一致)。
  • 批量导入/迁移:提供整命名空间 flush 或版本 bump 策略,并评估对在线流量的冲击。

穿透 · 击穿 · 雪崩

  • 穿透:恶意或稀疏键导致反复回源;空值缓存、布隆过滤器、接口层校验与速率限制组合使用。
  • 击穿:单热点键过期瞬间并发打穿;互斥锁、单飞(singleflight)、永不过期 + 异步刷新。
  • 雪崩:大量键同时过期或缓存集群故障;TTL 抖动、预热、分级降级与队列削峰。
# 缓存击穿防御:互斥锁(Redis SETNX 实现)
import threading, time, json
import redis

r = redis.Redis(host="localhost", port=6379, decode_responses=True)
_local_locks: dict[str, threading.Lock] = {}

def get_with_mutex(key: str, loader, ttl: int = 300):
    """单飞模式:同一进程内同一 key 只有一个 goroutine 回源"""
    cached = r.get(key)
    if cached is not None:
        return json.loads(cached)

    # 获取或创建本地锁(同进程内去重)
    lock = _local_locks.setdefault(key, threading.Lock())
    with lock:
        # Double check:拿到锁后再查一次
        cached = r.get(key)
        if cached is not None:
            return json.loads(cached)

        value = loader()  # 只有一个线程执行回源
        ttl_jitter = ttl + random.randint(-int(ttl * 0.1), int(ttl * 0.1))
        r.setex(key, ttl_jitter, json.dumps(value))
        return value

# 缓存穿透防御:Redis Bloom Filter(redis-py-bloom 或 RedisBloom 模块)
# 推荐使用 Redis Stack 的 BF.ADD / BF.EXISTS 命令
def bloom_get_user(user_id: int) -> dict | None:
    # 1. 先查布隆过滤器(误判率 0.1%,无漏判)
    exists = r.execute_command("BF.EXISTS", "users:bloom", user_id)
    if not exists:
        return None          # 确定不存在,直接返回,不查 DB

    # 2. 存在(可能误判)→ 走正常缓存逻辑
    key = f"user:{user_id}"
    cached = r.get(key)
    if cached:
        return json.loads(cached)

    user = db_get_user(user_id)
    if user:
        r.setex(key, 300, json.dumps(user))
    return user

# 新增用户时同步写入布隆过滤器
def create_user(user_id: int, data: dict):
    db_insert_user(user_id, data)
    r.execute_command("BF.ADD", "users:bloom", user_id)
# 分布式缓存一致性:先更新 DB 再删缓存(推荐模式)vs 先删缓存再更新 DB
# 模式 A:先更新 DB,再删缓存(Cache-Aside + 延迟双删)
def update_product_a(product_id: int, data: dict):
    db_update_product(product_id, data)     # 1. 先更新 DB
    cache_key = f"v2:product:{product_id}"
    r.delete(cache_key)                     # 2. 删缓存(主删)
    # 延迟双删:500ms 后再删一次,防止并发读写期间旧值回填
    time.sleep(0.5)
    r.delete(cache_key)

# 模式 B:先删缓存再更新 DB(不推荐)
# 问题:删除后到更新完成期间,并发读请求回填旧值到缓存 → 脏数据

# 取舍:
# - 模式 A:极短窗口(DB 更新到删除之间)可能读到旧缓存,但一般可接受
# - 模式 A 延迟双删:防止 DB 主从延迟期间副本读写旧值回填
# - 更强保证:消息队列异步删除,或 Binlog CDC 驱动失效(最终一致)

一致性与降级

在 SKILL 中为每条 API 标注期望一致性:线性一致读、因果一致、或最终一致可接受的最大延迟上界。

  • 跨层缓存时,下层失效必须能驱动上层可见更新,或通过极短 TTL 吸收差异。
  • 降级:缓存不可用时直接回源并可能触发更严限流;禁止无限重试压垮 DB。

Agent 遇到「金融级强一致」与「首页大流量」并存时,应拆路径:关键写读走主库短链路,展示类走可陈旧缓存。

可观测与告警

  • 指标:命中率、未命中 QPS、回源率、缓存操作延迟(p95/p99)、内存/连接数。
  • 追踪:在 trace 上标注 cache hit/miss、键前缀(脱敏),便于定位错误失效或热点。
  • 告警:命中率骤降、单键 QPS 异常、回源超时激增与「缓存层错误率」联动 on-call 手册。

键与 TTL 草稿工具

填写下方字段,生成分层缓存键草稿与新鲜度档位的 TTL 建议区间(示意,落地前须与团队约定对齐)。抖动按均匀分布计算示例区间。

新鲜度档位

              

键段经安全化:仅保留字母数字与 _ - .;空段以 unknown 代替。实际环境请再叠加 region、locale 等维度。

---
name: caching-strategy
description: 为读写场景设计分层缓存、失效规则与防护模式
---
# 分层策略
浏览器: Cache-Control private/public + max-age;含 PII 的响应强制 private
CDN: public + s-maxage + stale-while-revalidate;需登录内容不走 CDN
应用进程: 短 TTL LRU(如 60s),多实例一致性靠 TTL 自然过期
Redis: Cache-Aside 模式;TTL=基准+随机抖动(±10%);空值缓存 30s 防穿透
# Redis 数据结构选择
String: 单对象 JSON 序列化(用户信息、商品详情)
Hash: 会话字段、配置(支持单字段 HSET 更新)
List: 最近 N 条记录(LPUSH + LTRIM 维护固定长度)
Set: 标签去重、黑名单(O(1) SISMEMBER)
Sorted Set: 排行榜、带 TTL 优先级队列(ZADD + ZRANGEBYSCORE)
# 防护模式(代码级)
击穿: threading.Lock + Double Check → 同进程内只有一个线程回源
穿透: BF.EXISTS(Redis Bloom Filter)→ 确定不存在直接返回
雪崩: TTL = base + random.randint(-base*0.1, base*0.1)
# 写路径一致性
推荐: 先更新 DB → 删缓存 → 延迟 500ms 再删(防主从延迟回填)
强一致: 消息队列 / Binlog CDC 驱动异步删除 + 最终一致兜底
禁止: 两个服务各自写同一缓存 key 而无协调机制

返回技能库 更多技能入口