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 进行拒绝服务攻击。
冒烟测试案例:向缓存系统发送 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 秒随机值,观察数据库连接数峰值是否超过阈值。同时验证降级策略是否正常返回默认值或错误提示。