Redis 缓存设计:穿透、击穿、雪崩与实战

·CyreneStar
目录

缓存的基本读写

最常见的「Cache Aside」模式:

  1. 读:先查缓存,命中直接返回;未命中查库,写入缓存。
  2. 写:先更新数据库,再删除缓存(而非更新缓存)。
读: cache.get(k) → 命中返回; 未命中 db.query → cache.set(k, v, ttl)
写: db.update → cache.del(k)

缓存穿透:查根本不存在的数据

成因:请求的参数在数据库中不存在(如 id = -1),缓存和数据库都查不到,恶意流量直接打到 DB。

解法一:缓存空值

v = cache.get(k)
if v == nil:
    v = db.query(k)
    if v == nil:
        cache.set(k, NULL, 60s)   # 缓存短时间空标记
    return v

解法二:布隆过滤器——在缓存前加一层 BloomFilter,不存在的 key 直接拒绝。

if !bloom.mightContain(k): return nil   # 一定不存在

缓存击穿:热点 key 突然失效

成因:某个超热点 key(如首页配置)过期瞬间,海量请求同时穿透到 DB。

解法:互斥锁(singleflight)

v = cache.get(k)
if v == nil:
    if lock.tryAcquire(k):        # 只有一个线程回源
        v = db.query(k)
        cache.set(k, v, ttl)
        lock.release(k)
    else:
        sleep(50ms); retry()      # 其余线程等待并重试

另一种思路是逻辑过期:value 里带过期时间戳,异步刷新,读时不阻塞。

缓存雪崩:大量 key 同时失效

成因:批量 key 设置了相同 TTL,同一时刻集体失效,DB 瞬间被打垮。

解法:随机过期时间

ttl = base + random(0, jitter)    # 例如 3600 ± 600 秒
cache.set(k, v, ttl)

配合多级缓存(本地 Caffeine + Redis)与限流降级,可进一步兜底。

最佳实践小结

  • 给所有 key 设 TTL,避免无限堆积;
  • 写操作删缓存而非更新缓存;
  • 大 value 拆分,避免热 key 集中;
  • 监控命中率,命中率低于 90% 要警惕。

缓存的本质是用空间换时间,代价是一致性。明确业务能接受的最终一致窗口,再决定策略。

© 2026 CyreneStar · CyreneStar