3. 缓存失效策略:TTL过期、主动失效、被动失效、基于事件的失效机制

在分布式缓存与数据库并存的架构中,缓存失效策略直接决定了数据一致性的时效窗口与系统负载。Kubernetes 环境下,Pod 生命周期短、配置变更频繁,失效策略的选择需要兼顾缓存命中率、数据库压力与一致性等级。本章从四种核心失效机制出发,分析其原理、适用场景及在 K8s 中的部署要点。

3.1 四种失效策略对比总览

策略 触发方式 一致性时效 对数据库压力 K8s 部署适配性
TTL 过期 时间驱动(被动) 弱一致,窗口 = TTL 低(批量过期) 高,无外部依赖
主动失效 写操作后立即删除/更新缓存 强一致(写后即失效) 中(单条失效) 需保证写操作与失效操作的原子性
被动失效 读请求发现缓存缺失后回源 弱一致,依赖上游失效通知 高(缓存击穿风险) 需配合限流与回源保护
基于事件的失效 数据变更事件驱动(CDC / 消息队列) 最终一致,延迟可控 低(异步批量) 高,与 K8s 事件驱动架构天然契合

3.2 TTL 过期:最基础的失效策略

TTL(Time-To-Live)是缓存系统内置的自动过期机制。每个缓存条目设置一个存活时间,到期后自动失效。在 Redis 中通过 EXPIRE 命令实现,在 Memcached 中通过 set 命令的过期时间参数实现。

关键指标: TTL 设置过短 → 缓存命中率下降,数据库压力上升;TTL 设置过长 → 数据不一致窗口增大。建议根据业务容忍度设置 TTL,通常为 30s ~ 5min。
# Redis TTL 设置示例
SET user:1001 '{"name":"Alice","role":"admin"}' EX 120   # 120秒后自动过期

K8s 部署注意事项:

  • Redis / Memcached 作为 StatefulSet 部署时,需确保 TTL 机制不受 Pod 重启影响(RDB/AOF 持久化)。
  • 避免所有缓存条目设置相同 TTL,防止缓存雪崩。建议在基础 TTL 上增加 ±10%~20% 的随机偏移。
  • TTL 过期适合读多写少、对一致性要求不高的场景,如商品详情页、配置信息。
TTL 过期生命周期 TTL 过期生命周期 写入缓存 TTL 计时中 TTL 到期 自动删除 说明:TTL 过期是时间驱动的被动失效,无需外部触发,适合一致性要求不高的场景。 在 K8s 中需注意缓存雪崩问题,建议 TTL 加入随机偏移。 T0 T0 + TTL T0 + TTL + 清理延迟

3.3 主动失效:写操作驱动的强一致性策略

主动失效是指在数据库写入操作完成后,立即对缓存中对应的条目执行删除或更新操作。这是实现缓存与数据库强一致性的最直接手段。

最佳实践: 在 K8s 微服务架构中,建议将缓存失效操作与数据库写操作放在同一个事务边界内(如使用本地事务 + 消息队列),避免写库成功但失效失败导致的数据不一致。
// 主动失效伪代码(Go 示例)
func UpdateUser(db *sql.DB, cache *redis.Client, userID string, newData User) error {
    tx, _ := db.Begin()
    // 1. 更新数据库
    if err := tx.Exec("UPDATE users SET ... WHERE id=?", userID); err != nil {
        tx.Rollback()
        return err
    }
    // 2. 立即删除缓存(主动失效)
    if err := cache.Del(ctx, "user:"+userID).Err(); err != nil {
        tx.Rollback()
        return err
    }
    return tx.Commit()
}

主动失效的三种实现模式:

  • Cache-Aside 模式: 应用层在写库后主动删除缓存,下次读请求触发回源。
  • Write-Through 模式: 写操作同时更新数据库和缓存,保证两者一致。
  • Write-Behind 模式: 写操作先更新缓存,异步批量写入数据库,适用于写吞吐量高的场景。

3.4 被动失效:读请求驱动的回源策略

被动失效是指缓存条目在未被主动删除或 TTL 过期的情况下,由读请求发现缓存缺失后触发回源加载。这种策略通常与主动失效配合使用,作为兜底机制。

风险提示: 被动失效在高并发场景下容易引发缓存击穿(Cache Breakdown)。当某个热点 key 失效时,大量并发请求同时回源数据库,可能导致数据库过载。K8s 中建议配合限流(Rate Limiter)或分布式锁(如 Redis Redlock)保护数据库。
# 被动失效 + 分布式锁回源(Python 示例)
def get_user(user_id):
    cache_key = f"user:{user_id}"
    data = redis.get(cache_key)
    if data:
        return data
    # 缓存缺失,尝试获取分布式锁
    lock_key = f"lock:{cache_key}"
    if redis.setnx(lock_key, "1", ex=5):  # 5秒超时
        data = db.query("SELECT * FROM users WHERE id=?", user_id)
        redis.setex(cache_key, 120, data)  # 回源后写入缓存
        redis.delete(lock_key)
        return data
    else:
        # 等待其他线程回源完成
        time.sleep(0.1)
        return get_user(user_id)  # 递归重试

3.5 基于事件的失效:异步解耦的最终一致性方案

基于事件的失效机制通过捕获数据变更事件(如数据库 binlog、K8s ConfigMap 变更事件),异步通知缓存系统执行失效操作。这种策略在微服务架构中尤为流行,因为它实现了写操作与缓存失效的解耦。

基于事件的失效架构 基于事件的缓存失效架构 应用服务 写操作 数据库 MySQL / PostgreSQL CDC 捕获器 Debezium / Canal 消息队列 Kafka / RabbitMQ 缓存失效消费者 删除 / 更新缓存 Redis / Memcached 优势:写操作与缓存失效完全解耦,支持异步批量处理,适合 K8s 事件驱动架构。

K8s 中的事件源示例:

  • 数据库 CDC 事件: 使用 Debezium 监听 MySQL binlog,将变更事件发送到 Kafka,缓存消费者订阅后执行失效。
  • K8s ConfigMap / Secret 变更事件: 使用 Informer 监听资源变化,触发配置缓存的失效。
  • 自定义业务事件: 微服务通过 CloudEvents 规范发布数据变更事件,缓存服务订阅后处理。
# K8s ConfigMap 变更事件监听(Go 示例)
import (
    "k8s.io/client-go/informers"
    "k8s.io/client-go/tools/cache"
)

func WatchConfigMapCache() {
    factory := informers.NewSharedInformerFactory(clientset, 0)
    informer := factory.Core().V1().ConfigMaps().Informer()
    informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
        UpdateFunc: func(oldObj, newObj interface{}) {
            cm := newObj.(*v1.ConfigMap)
            // 失效对应配置缓存
            redis.Del(ctx, "config:"+cm.Name)
        },
    })
    stopCh := make(chan struct{})
    informer.Run(stopCh)
}

3.6 策略选择矩阵

业务场景 推荐策略 理由
商品详情页(读多写少) TTL 过期 + 主动失效 TTL 兜底,主动失效保证价格/库存更新后立即生效
用户会话(Session) TTL 过期 会话天然有时效性,无需主动失效
配置中心(ConfigMap) 基于事件的失效 K8s 原生事件驱动,配置变更后自动失效缓存
热点数据(秒杀/排行榜) 主动失效 + 被动失效兜底 主动失效保证一致性,被动失效+分布式锁防止击穿
跨微服务数据共享 基于事件的失效 解耦服务间依赖,异步处理变更事件
K8s 部署建议: 在 Kubernetes 环境中,推荐采用混合策略:核心业务数据使用主动失效 + TTL 兜底,配置类数据使用基于事件的失效,热点数据增加被动失效的限流保护。所有失效策略都应通过 Prometheus 指标监控(如失效次数、回源延迟、缓存命中率),以便在 Dashboard 中实时调整策略参数。