4. 本地缓存+Redis两级缓存架构
4.1 两级缓存架构概述
在热点数据访问场景中,单一 Redis 缓存仍面临网络开销与序列化延迟。引入本地缓存(如 Caffeine、Guava Cache)作为一级缓存(L1),Redis 作为二级缓存(L2),形成两级缓存架构,可显著降低热点数据的访问延迟,减少 Redis 连接压力。
核心收益: 本地缓存命中时延 < 1ms(堆内访问),Redis 缓存命中时延约 1-5ms(网络+序列化),两级缓存整体吞吐量可提升 3-10 倍。
4.2 一级缓存选型:Caffeine vs Guava Cache
| 对比维度 | Caffeine | Guava Cache |
|---|---|---|
| 性能 | 基于 W-TinyLFU 算法,读写吞吐量更高 | 基于 LRU,高并发下锁竞争较明显 |
| 内存管理 | 支持基于频率和大小的驱逐策略,更精准 | 仅支持基于容量/时间的驱逐 |
| 异步加载 | 原生支持异步 LoadingCache | 需手动封装 |
| 统计监控 | 内置 hit rate、miss rate、eviction count | 基础统计,功能较弱 |
| 推荐场景 | 高并发、热点频繁变动的业务 | 低并发、简单缓存需求 |
选型建议: 新项目优先选择 Caffeine,其性能在 JDK 8+ 环境下比 Guava Cache 提升约 30%-50%。若已有 Guava 依赖且并发量不高,可继续使用。
4.3 两级缓存一致性策略
两级缓存的核心挑战是 数据一致性。本地缓存与 Redis 之间、Redis 与数据库之间都可能出现数据不一致。常用策略包括 TTL 过期、主动失效、以及组合方案。
4.3.1 TTL 过期策略
为本地缓存和 Redis 分别设置不同的过期时间,利用时间差实现最终一致性。
- 本地缓存 TTL:通常设置为 5-30 秒,较短,保证数据相对新鲜。
- Redis TTL:通常设置为 5-60 分钟,较长,作为稳定共享层。
- 效果:本地缓存过期后自动从 Redis 拉取最新数据,实现最终一致。
// Caffeine 本地缓存配置示例
Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.SECONDS) // 本地缓存 10 秒过期
.recordStats()
.build();
// Redis 缓存配置(Spring Data Redis 示例)
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // Redis 缓存 30 分钟过期
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
4.3.2 主动失效策略
当数据发生写操作(更新/删除)时,主动通知各级缓存失效,避免脏数据。
- 失效流程:写请求 → 更新数据库 → 删除 Redis 缓存 → 广播失效消息(如 Redis Pub/Sub 或 MQ)→ 各服务节点删除本地缓存。
- 关键点:先更新数据库,再删除缓存,避免并发读写导致的不一致。
注意: 主动失效策略需要处理消息丢失或重复消费问题。建议结合 TTL 兜底,即使失效消息丢失,本地缓存也会在 TTL 到期后自动刷新。
4.3.3 组合策略:TTL + 主动失效
生产环境推荐组合使用,兼顾实时性与可靠性。
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 仅 TTL | 实现简单,无额外依赖 | 数据更新延迟大(最长 TTL 时间) | 读多写少、对实时性要求不高的场景 |
| 仅主动失效 | 实时性高,数据一致性好 | 依赖消息中间件,增加系统复杂度 | 写频繁、对一致性要求高的场景 |
| TTL + 主动失效 | 实时性高 + 兜底保障 | 实现复杂度中等 | 推荐: 大多数企业级业务 |
4.4 两级缓存代码实现示例
以下展示一个完整的 Caffeine + Redis 两级缓存工具类,包含读穿透、写失效、TTL 兜底。
@Component
public class TwoLevelCache<K, V> {
private final Cache<K, V> localCache;
private final RedisTemplate<K, V> redisTemplate;
private final StringRedisTemplate stringRedisTemplate;
private final long redisTtlSeconds;
public TwoLevelCache(RedisTemplate<K, V> redisTemplate,
StringRedisTemplate stringRedisTemplate,
long redisTtlSeconds) {
this.localCache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.SECONDS) // L1 TTL 10秒
.recordStats()
.build();
this.redisTemplate = redisTemplate;
this.stringRedisTemplate = stringRedisTemplate;
this.redisTtlSeconds = redisTtlSeconds;
}
// 读操作:L1 -> L2 -> DB
public V get(K key, Function<K, V> dbLoader) {
// 1. 查本地缓存
V value = localCache.getIfPresent(key);
if (value != null) {
return value;
}
// 2. 查 Redis
value = redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value); // 回填 L1
return value;
}
// 3. 查数据库
value = dbLoader.apply(key);
if (value != null) {
redisTemplate.opsForValue().set(key, value, redisTtlSeconds, TimeUnit.SECONDS);
localCache.put(key, value);
}
return value;
}
// 写操作:更新 DB -> 删除 L2 -> 广播失效 L1
public void put(K key, V value, Consumer<K> dbUpdater) {
// 1. 更新数据库
dbUpdater.accept(key);
// 2. 删除 Redis 缓存
redisTemplate.delete(key);
// 3. 广播失效消息(所有节点删除本地缓存)
stringRedisTemplate.convertAndSend("cache:invalidate", key.toString());
// 4. 删除当前节点本地缓存
localCache.invalidate(key);
}
// 监听失效消息(在 @RedisListener 中调用)
@RedisListener(channel = "cache:invalidate")
public void handleInvalidateMessage(K key) {
localCache.invalidate(key);
}
}
4.5 性能调优要点
- 本地缓存大小:根据 JVM 堆内存设置,通常不超过 100MB,避免 GC 压力。
- TTL 差异化:热点数据本地 TTL 可适当延长(如 30 秒),冷数据缩短(如 5 秒)。
- 缓存穿透防护:本地缓存和 Redis 均缓存空值(设置短 TTL),防止恶意 key 穿透。
- 缓存雪崩防护:本地缓存 TTL 添加随机偏移(±20%),避免同时过期。
- 监控告警:通过 Caffeine 的
recordStats()监控本地缓存命中率,低于 80% 时触发告警。
生产案例: 某电商平台商品详情页采用 Caffeine(L1 TTL 15秒)+ Redis(L2 TTL 1小时)两级缓存,本地缓存命中率从 65% 提升至 92%,Redis QPS 下降 70%,接口平均响应时间从 12ms 降至 2ms。
4.6 两级缓存架构总结
| 层级 | 缓存组件 | TTL 建议 | 容量建议 | 一致性策略 |
|---|---|---|---|---|
| L1 本地缓存 | Caffeine / Guava Cache | 5-30 秒 | 10-100 MB | TTL 过期 + 主动失效 |
| L2 Redis 缓存 | Redis Cluster / Sentinel | 5-60 分钟 | 根据业务数据量 | TTL 过期 + 主动删除 |
| 数据库 | MySQL / PostgreSQL | — | — | 作为最终数据源 |
两级缓存架构通过本地缓存扛住热点流量、Redis 提供共享数据层、数据库作为持久化底座,配合 TTL 与主动失效的组合策略,能够在高并发场景下实现高性能与数据一致性的平衡。实际落地时需根据业务读写比例、数据一致性要求、运维成本等因素灵活调整参数。