共计 3154 个字符,预计需要花费 8 分钟才能阅读完成。
在高性能、高并发的系统架构中,缓存系统不仅是性能保障的核心,更是“服务稳定性”的关键支撑点。单一 Redis 已无法满足对低延迟和高可用性的极致追求,因此,“本地缓存 + 分布式缓存”的双层架构成为主流选择。
本文将带你系统地深入了解:
- ✅ 双层缓存架构的核心设计
- ✅ 高级特性与工程化封装
- ✅ 易错陷阱与解决方案
- ✅ 弹性与容错能力提升方案
一、混合缓存的本质价值
我们希望达成的目标:
维度 | 要求 |
---|---|
性能 | 99% 请求纳秒级响应 |
一致性 | 缓存数据无显著滞后 |
稳定性 | Redis 故障不影响核心功能 |
扩展性 | 可动态调整缓存策略与节点数 |
➡️ Redis 负责共享、本地缓存负责极速访问、两者组合形成多级缓存闭环。
二、混合缓存架构设计图

三、完整源码封装
通用缓存访问逻辑
public <T> T get(String key, Class<T> clazz, Supplier<T> dbLoader) {
// 1. 本地缓存
Object local = localCache.getIfPresent(key);
if (local != null) return clazz.cast(local);
// 2. Redis
String redis = redisTemplate.opsForValue().get(key);
if (redis != null) {
T val = JsonUtil.fromJson(redis, clazz);
localCache.put(key, val);
return val;
}
// 3. DB Fallback
T val = dbLoader.get();
if (val != null) {
localCache.put(key, val);
redisTemplate.opsForValue().set(key, JsonUtil.toJson(val), Duration.ofMinutes(10));
}
return val;
}
四、常见问题与基本解决方案
1. 多节点本地缓存一致性问题
问题场景:
A 节点写入 Redis 后,B 节点的本地缓存仍然是旧值,数据脏读
解决方案:使用 Redis 发布订阅机制通知其他节点清理本地缓存
// 写入时
redisTemplate.convertAndSend("cache:evict", key);
// 监听器
@EventListener
public void onMessage(Message message) {
String key = message.toString();
localCache.invalidate(key);
}
2. 缓存雪崩
问题场景:
大量缓存同时过期,全部打到数据库或 Redis 上
解决方案:设置缓存过期时间加上 随机扰动,避免同一时间集中过期
long ttl = 600 + ThreadLocalRandom.current().nextInt(60); // 600s ± 60s
redisTemplate.opsForValue().set(key, json, Duration.ofSeconds(ttl));
3. 缓存穿透
问题场景:
请求的是数据库不存在的数据,频繁请求打到底层数据库
解决方案:
- 将 null 值也缓存(短 TTL)
- 或使用 布隆过滤器 拦截非法 Key
if (dbVal == null) {
redisTemplate.opsForValue().set(key, "NULL", Duration.ofSeconds(30));
return null;
}
4. 缓存击穿(热点 Key 遭遇并发访问)
public <T> T getWithMutex(String key, Class<T> clazz, Supplier<T> dbLoader) {
// 1. 尝试从缓存读取
String cacheVal = redisTemplate.opsForValue().get(key);
if (cacheVal != null) {
return JsonUtil.fromJson(cacheVal, clazz);
}
// 2. 分布式锁防止击穿
RLock lock = redissonClient.getLock("lock:cache:" + key);
try {
boolean locked = lock.tryLock(10, 5, TimeUnit.SECONDS); // 最多等10s,锁5s释放
if (locked) {
// double-check:再次检查缓存
cacheVal = redisTemplate.opsForValue().get(key);
if (cacheVal != null) {
return JsonUtil.fromJson(cacheVal, clazz);
}
// 查询数据库并写入缓存
T dbVal = dbLoader.get();
if (dbVal != null) {
redisTemplate.opsForValue().set(key, JsonUtil.toJson(dbVal), Duration.ofMinutes(10));
} else {
// null缓存防穿透
redisTemplate.opsForValue().set(key, "NULL", Duration.ofSeconds(30));
}
return dbVal;
} else {
// 未拿到锁,可选择等待一小段时间后重试(或立即返回 null / fallback)
Thread.sleep(50); // 简化策略
return getWithMutex(key, clazz, dbLoader); // retry
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
5. Redis与MySQL数据不一致
这里就不细说了,后续可能会开一篇文章详细讨论
- 缓存更新采用双删 + 延迟删策略
- 引入消息队列或 Canal 监听 DB binlog 同步更新
五、架构级别的扩展设计
1. 支持异步刷新(refresh-after-write)
// 配置 Caffeine 支持异步 refresh
Caffeine.newBuilder()
.refreshAfterWrite(3, TimeUnit.MinUTES)
.buildAsync(new AsyncCacheLoader<String, Object>() {
public CompletableFuture<Object> asyncLoad(String key, Executor executor) {
return CompletableFuture.supplyAsync(() -> remoteLoader(key), executor);
}
});
2. 支持统一缓存监控(命中率、TTL、命中层级)
- 统计本地命中/Redis 命中/DB 查询次数
- 将日志写入监控平台(如 Prometheus + Grafana)
3. 缓存注解化(自动识别缓存策略)
使用自定义注解 @HybridCache(key="user:{id}", ttl=600)
自动处理缓存读取逻辑。
六、实战经验小结
问题 | 推荐做法 |
---|---|
本地缓存过期如何控制? | 设置 TTL、refresh、监听 Redis |
Redis 挂了怎么办? | 降级到本地缓存(弱一致性兜底) |
多线程高并发怎么处理? | 加锁、预热、异步刷缓存 |
如何保证低延迟和一致性 | 异步刷新 + 通知同步 + TTL 折中 |
七、总结
本地缓存 + Redis 分布式缓存的混合架构,不再只是“加一层缓存”这么简单,它是一个具备:
- 低延迟访问
- 分布式一致性保障
- 高可用容灾容错
- 支持动态扩容与监控
- 适配多业务数据模型
的综合解决方案。
✅ 适合所有对性能敏感的中大型业务系统,特别是在权限缓存、热点配置、组织树等场景。
我是 李卷卷,专注Java相关知识输出。感谢阅读!
正文完