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 响应缓存。
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 契约设计
单一策略难以满足所有需求。生产系统通常采用分层组合方式:
- 第一层(时间维度):TTL 确保数据时效性,防止脏数据长期驻留。
- 第二层(空间维度):LRU 或 LFU 控制内存上限,优先保留热点。
- 第三层(兜底):随机淘汰或 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 随机偏移百分比,防止缓存雪崩"
最佳实践:在 OpenAPI 契约中,建议将
eviction_policy 设为可枚举参数,并允许客户端按接口粒度指定。例如:高频查询接口使用 lru,低频批量接口使用 fifo 或 ttl_only。