3. 缓存失效策略总览:TTL、LRU、LFU、FIFO、随机淘汰策略的原理与适用场景

缓存失效策略是缓存系统的核心治理手段,决定了数据何时被移除、哪些数据被优先淘汰。不同的策略在命中率、计算开销、内存抖动和一致性风险上存在显著差异。本节系统梳理五种主流失效策略的原理与适用场景,帮助你在 OpenAPI 契约设计中做出合理选择。

3.1 策略分类与核心指标

失效策略可分为两大类:时间驱动(TTL)和空间驱动(LRU、LFU、FIFO、随机)。时间驱动关注数据时效性,空间驱动关注内存容量约束。实际系统中通常组合使用。

策略 淘汰依据 时间复杂度 内存开销 命中率特征
TTL(生存时间) 写入时间 + 固定时长 O(1) 低(仅时间戳) 依赖业务预判,过期即失效
LRU(最近最少使用) 最近访问时间 O(1)(链表+哈希) 中(双向链表指针) 热点数据集中时极高
LFU(最不经常使用) 访问频率 O(log n) 或 O(1) 高(频率计数器) 长期热点稳定时最优
FIFO(先进先出) 写入顺序 O(1) 低(队列指针) 无访问感知,命中率较低
随机淘汰 随机选择 O(1) 极低 不可预测,仅用于兜底
设计建议:在 OpenAPI 契约中,建议将 TTL 作为必选失效维度,空间淘汰策略作为可选配置项。例如:cache-ttl: 300 + eviction-policy: lru。

3.2 TTL(生存时间)策略

TTL 是最直观的失效策略:每个缓存条目关联一个过期时间戳,到达时间后自动失效。TTL 不依赖访问模式,完全由业务语义决定。

  • 原理:写入时记录 expire_at = now + ttl,读取时检查是否过期。
  • 优点:实现简单、可预测、适合强时效性数据(如验证码、会话令牌)。
  • 缺点:无法应对突发流量导致的缓存雪崩;固定 TTL 难以适配所有数据。
  • 适用场景:API 限流计数器、OAuth 令牌缓存、短时热点新闻。
// TTL 失效伪代码
func get(key string) (value interface{}, ok bool) {
    entry := cache[key]
    if entry == nil {
        return nil, false
    }
    if time.Now().Unix() > entry.expireAt {
        delete(cache, key)
        return nil, false
    }
    return entry.value, true
}
避坑提示:大量 key 在同一时刻过期会导致缓存雪崩。建议在 TTL 基础上增加随机偏移量(±10%~20%),或使用渐进式过期扫描。

3.3 LRU(最近最少使用)策略

LRU 基于“最近访问时间”淘汰数据:当缓存满时,移除最久未被访问的条目。LRU 利用时间局部性原理,是生产环境最常用的空间淘汰策略。

  • 原理:使用双向链表 + 哈希表维护访问顺序。每次访问将节点移到链表头部,淘汰时移除链表尾部。
  • 优点:对突发热点响应快;实现成熟(LinkedHashMap、Redis 近似 LRU)。
  • 缺点:偶发扫描大量冷数据会污染缓存;无法区分高频与低频访问。
  • 适用场景:用户会话缓存、商品详情页缓存、API 响应缓存。
LRU 双向链表 + 哈希表结构 哈希表 key → 节点指针 user:1001 → NodeA product:42 → NodeB order:88 → NodeC session:x7 → NodeD ... → ... HEAD(最近访问) user:1001 NodeA product:42 NodeB order:88 NodeC session:x7 NodeD(尾部) 最久未访问 TAIL(淘汰) 移除此处 淘汰流程: 当缓存满时,移除 TAIL 指向的 NodeD;新访问的节点移动到 HEAD 之后。哈希表提供 O(1) 的节点定位。

3.4 LFU(最不经常使用)策略

LFU 根据访问频率淘汰数据:缓存满时移除访问次数最少的条目。LFU 能长期保留高频热点,但存在“缓存污染”和“频率老化”问题。

  • 原理:每个条目维护访问计数器,淘汰时选择计数最小的 key。常用最小堆或多级链表实现。
  • 优点:长期热点命中率极高;抗突发扫描能力强。
  • 缺点:旧热点数据即使不再被访问,仍因历史高频率占据缓存;实现复杂度高。
  • 适用场景:CDN 边缘节点、数据库查询缓存、长期稳定的推荐系统。
LFU 优化变体:Window-TinyLFU(Redis 4.0 使用)引入频率衰减机制,定期将计数器减半,解决历史热点残留问题。同时使用 Bloom Filter 统计新 key 的访问趋势。

3.5 FIFO(先进先出)与随机淘汰策略

FIFO 和随机淘汰是两种简单但命中率较低的策略,通常作为兜底方案或特定场景下的轻量选择。

策略 实现方式 命中率 典型场景
FIFO 队列维护写入顺序,淘汰队首 低(不感知访问) 日志缓存、消息队列偏移量缓存
随机淘汰 随机选择 key 删除 不可预测 Redis 配置 allkeys-random、内存压力兜底
  • FIFO 适用场景:数据写入后只被读取一次或很少读取(如一次性令牌、事件日志)。FIFO 保证最新数据始终在缓存中。
  • 随机淘汰适用场景:当缓存命中率不是首要目标,但需要极低的内存开销和 CPU 消耗时。Redis 的 maxmemory-policy volatile-random 常用于非关键缓存。
避坑提示:随机淘汰可能导致热点数据被意外清除,引发缓存穿透。生产环境建议仅在 TTL 较短(< 60s)且数据可快速重建的场景下使用。

3.6 策略组合与 OpenAPI 契约设计

单一策略难以满足所有需求。生产系统通常采用分层组合方式:

  1. 第一层(时间维度):TTL 确保数据时效性,防止脏数据长期驻留。
  2. 第二层(空间维度):LRU 或 LFU 控制内存上限,优先保留热点。
  3. 第三层(兜底):随机淘汰或 FIFO 在极端内存压力下保证系统不崩溃。
// OpenAPI 契约中的缓存策略配置示例
CacheConfig:
  type: object
  properties:
    ttl_seconds:
      type: integer
      minimum: 1
      maximum: 86400
      default: 300
    eviction_policy:
      type: string
      enum: [lru, lfu, fifo, random, ttl_only]
      default: lru
    max_entries:
      type: integer
      minimum: 100
      default: 10000
    ttl_jitter_percent:
      type: integer
      minimum: 0
      maximum: 50
      default: 10
      description: "TTL 随机偏移百分比,防止缓存雪崩"
缓存失效策略分层组合架构 第一层:TTL 时间失效 过期时间戳检查 → 主动失效 + 惰性删除 第二层:LRU / LFU 空间淘汰 内存超限时按访问模式淘汰冷数据 第三层:FIFO / 随机淘汰(兜底) 极端内存压力下保证系统不 OOM
最佳实践:在 OpenAPI 契约中,建议将 eviction_policy 设为可枚举参数,并允许客户端按接口粒度指定。例如:高频查询接口使用 lru,低频批量接口使用 fifo 或 ttl_only。