1. 缓存工程概述

1.1 缓存概念与核心作用

缓存(Cache)是位于数据请求路径上的临时存储层,用于保存频繁访问的数据副本。当系统请求数据时,优先从缓存中读取,若命中则直接返回,避免每次请求都穿透到后端数据库或远程服务。

维度 说明 典型收益
延迟 内存访问延迟通常在微秒级,远低于磁盘 I/O 或网络 RPC 读延迟降低 80%~95%
吞吐 缓存层可承载每秒数万至数十万 QPS,数据库通常仅数千 系统吞吐提升 10~100 倍
保护后端 过滤重复请求,减少数据库连接池压力与慢查询 数据库负载降低 60%~90%
成本 用少量内存/SSD 替代频繁的数据库扩容 硬件与运维成本显著下降
工程视角:缓存不是银弹。引入缓存意味着数据一致性、过期策略、内存管理、缓存穿透防护等新问题必须一并解决。回归冒烟测试中,缓存行为验证是核心关注点之一。

1.2 常见缓存策略

LRU(Least Recently Used)

LRU 是最常用的缓存淘汰策略。当缓存空间满时,淘汰最久未被访问的数据。实现方式通常为哈希表 + 双向链表(如 LinkedHashMap),保证 get/put 操作时间复杂度为 O(1)。

// 简化版 LRU 缓存核心逻辑
public class LRUCache<K, V> {
    private final int capacity;
    private final LinkedHashMap<K, V> map;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.map = new LinkedHashMap<>(capacity, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                return size() > capacity;
            }
        };
    }

    public V get(K key) {
        return map.get(key);
    }

    public void put(K key, V value) {
        map.put(key, value);
    }
}

TTL(Time To Live)

TTL 为每个缓存条目设置生存时间,到期后自动失效。适用于数据时效性要求明确的场景,如会话信息、验证码、热点新闻列表。TTL 过短会导致缓存命中率下降,过长则可能返回过期数据。

策略 淘汰依据 适用场景 缺点
LRU 访问时间 通用热点数据、用户会话 偶发全量扫描可能污染缓存
TTL 写入时间 验证码、排行榜、价格信息 过期时间难以精确设定
LFU 访问频率 长期稳定热点数据 实现复杂,内存开销大
FIFO 写入顺序 日志缓存、批量写入 可能淘汰仍频繁使用的数据
回归冒烟要点:测试缓存策略时,需验证 LRU 淘汰顺序是否正确、TTL 过期后数据是否被清除、以及并发场景下淘汰逻辑是否线程安全。

1.3 缓存三大经典问题

缓存穿透、缓存击穿、缓存雪崩是缓存工程中最常遇到的故障模式,也是回归冒烟测试必须覆盖的边界场景。

缓存三大经典问题对比 缓存穿透 请求不存在的数据 缓存未命中 → 数据库未命中 恶意攻击或非法 key 解决:布隆过滤器 + 空值缓存 缓存击穿 热点 key 过期瞬间 大量并发请求穿透到 DB 单点高并发压力 解决:互斥锁 / 逻辑过期 缓存雪崩 大量 key 同时过期 或缓存节点宕机 数据库被压垮 解决:过期时间加随机值 + 降级 回归冒烟测试覆盖要点 • 穿透:构造不存在的 key 批量请求,验证布隆过滤器拦截率 > 99% • 击穿:对热点 key 设置短 TTL,并发 100 线程请求,验证 DB 查询次数 ≤ 1 • 雪崩:模拟 50 个 key 同时过期,观察 DB 连接池是否被打满 • 恢复:缓存节点重启后,验证缓存预热逻辑是否按预期加载热点数据

缓存穿透

请求查询一个数据库中也不存在的数据。缓存和数据库都无法命中,每次请求都直接打到数据库。攻击者可以利用此特点构造大量不存在的 key 进行拒绝服务攻击。

冒烟测试案例:向缓存系统发送 10000 个随机不存在的用户 ID,验证数据库查询次数是否接近 0(布隆过滤器拦截)或仅出现少量空值缓存写入。

缓存击穿

某个热点 key 在过期瞬间,大量并发请求同时发现缓存失效,全部穿透到数据库。与穿透不同,击穿针对的是存在但刚过期的热点数据。

// 互斥锁方案(伪代码)
public String getData(String key) {
    String value = cache.get(key);
    if (value != null) return value;

    // 尝试获取分布式锁
    if (lock.tryLock(key)) {
        try {
            // 双重检查:锁获取后再次查询缓存
            value = cache.get(key);
            if (value != null) return value;

            value = db.query(key);
            cache.set(key, value, 300);
            return value;
        } finally {
            lock.unlock(key);
        }
    } else {
        // 未获取到锁的线程等待后重试
        Thread.sleep(50);
        return getData(key);
    }
}

缓存雪崩

大量缓存 key 在同一时间过期,或缓存节点发生故障宕机,导致所有请求直接涌向数据库,造成数据库压力激增甚至崩溃。

触发原因 影响范围 防护手段
批量 key 同时过期 部分业务数据不可用 过期时间加随机值(基础 TTL ± 随机秒)
缓存节点宕机 全站缓存失效 缓存集群高可用(主从 + 哨兵 / 集群分片)
网络分区 部分节点不可达 本地缓存兜底 + 熔断降级
缓存服务重启 缓存完全清空 缓存预热 + 渐进式加载
工程实践:在回归冒烟测试中,建议构造“缓存雪崩模拟场景”——设置 100 个 key 的 TTL 为 1~5 秒随机值,观察数据库连接数峰值是否超过阈值。同时验证降级策略是否正常返回默认值或错误提示。