一、前言:为什么单层缓存不够用?
你可能已经用 Redis 缓解了数据库压力,但在高并发场景下仍面临:
- ❌ Redis 网络延迟高(0.5~2ms),无法满足 <1ms 的响应要求
- ❌ 热点数据反复穿透到 Redis,造成不必要的网络开销
- ❌ 突发流量打垮缓存层
多级缓存(Multi-Level Cache) 正是为解决这些问题而生------通过"本地 + 分布式"协同,层层拦截请求,极致提升性能!
本文将手把手教你在 Spring Boot 中实现 Caffeine + Redis 多级缓存,并解决一致性、穿透、雪崩等核心问题。
二、多级缓存架构设计
2.1 典型三级架构

2.2 各层职责
| 层级 | 技术 | 作用 |
|---|---|---|
| L1 | Caffeine | 拦截本机热点请求,微秒级响应 |
| L2 | Redis Cluster | 共享缓存,避免 DB 直接暴露 |
| L3 | MySQL | 持久化存储 |
✅ 核心原则 :先查快的,再查慢的,尽量不让请求落到数据库!
三、第一步:添加依赖
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
四、第二步:配置 Caffeine 本地缓存
java
@Configuration
public class CacheConfig {
@Bean("localUserCache")
public Cache<Long, User> localUserCache() {
return Caffeine.newBuilder()
.maximumSize(10_000) // 最多缓存 1 万条
.expireAfterWrite(5, TimeUnit.MINUTES) // 本地缓存 TTL 较短
.recordStats() // 开启统计
.build();
}
}
🔑 关键设计 :本地缓存 TTL << Redis TTL(如 5min vs 30min),降低脏数据风险。
五、第三步:实现多级缓存 Service
java
@Service
public class UserService {
@Autowired
private Cache<Long, User> localUserCache;
@Autowired
private RedisTemplate<String, User> redisTemplate;
@Autowired
private UserMapper userMapper;
private static final String REDIS_KEY_PREFIX = "user:";
public User getUser(Long userId) {
// 1. 查 L1 本地缓存
User user = localUserCache.getIfPresent(userId);
if (user != null) {
return user;
}
// 2. 查 L2 Redis
String redisKey = REDIS_KEY_PREFIX + userId;
user = redisTemplate.opsForValue().get(redisKey);
if (user != null) {
// 回填本地缓存
localUserCache.put(userId, user);
return user;
}
// 3. 查 L3 数据库
user = userMapper.selectById(userId);
if (user != null) {
// 写入 Redis(TTL 30 分钟)
redisTemplate.opsForValue().set(redisKey, user, 30, TimeUnit.MINUTES);
// 写入本地缓存(TTL 5 分钟)
localUserCache.put(userId, user);
}
return user;
}
// 更新时清除各级缓存
public void updateUser(User user) {
userMapper.updateById(user);
// 清除 Redis
redisTemplate.delete(REDIS_KEY_PREFIX + user.getId());
// 清除本地缓存(仅本机)
localUserCache.invalidate(user.getId());
// 注意:其他机器的本地缓存无法清除 → 接受短暂不一致
}
}
六、解决三大核心挑战
挑战 1️⃣:缓存一致性
- 问题:更新 DB 后,其他机器的本地缓存仍是旧值
- 解决方案 :
-
短 TTL:本地缓存自动过期(最终一致)
-
主动失效 :通过 MQ 广播清除指令(高级方案)
java// 发送缓存失效消息 mqProducer.send("cache-invalidate", "user:" + userId); // 其他节点监听并执行 localCache.invalidate(userId);
-
挑战 2️⃣:缓存穿透
- 问题:查询不存在的用户 ID,反复打到 DB
- 解决方案 :
-
空值缓存 :
javaif (user == null) { redisTemplate.opsForValue().set(redisKey, NULL_PLACEHOLDER, 2, TimeUnit.MINUTES); localUserCache.put(userId, NULL_PLACEHOLDER); } -
布隆过滤器:预加载所有有效 ID
-
挑战 3️⃣:缓存雪崩 & 击穿
-
雪崩 :大量 key 同时过期
解决 :TTL 加随机值javalong ttl = 30 + new Random().nextInt(10); // 30~40 分钟 redisTemplate.expire(redisKey, ttl, TimeUnit.MINUTES); -
击穿 :热点 key 过期瞬间高并发
解决:本地缓存 + Redis 双重保护(本地缓存扛住瞬时流量)
七、性能对比:单级 vs 多级缓存
| 指标 | 仅 Redis | Caffeine + Redis |
|---|---|---|
| 平均延迟 | 1.2 ms | 0.3 ms |
| Redis QPS | 100,000 | 10,000 |
| 数据库 QPS | 5,000 | 500 |
| P99 延迟 | 3.5 ms | 0.8 ms |
📊 结论 :多级缓存可降低 90%+ 的下游压力,同时提升 3~4 倍响应速度!
八、生产环境最佳实践
✅ 监控指标
- 本地缓存命中率(目标 > 80%)
- Redis 命中率(目标 > 95%)
- 缓存加载失败次数
✅ 适用数据类型
- ✅ 配置信息(运营开关)
- ✅ 字典表(国家、城市)
- ✅ 热点商品/文章
- ❌ 用户私有数据(如购物车)→ 用 Redis 即可
✅ 容量控制
- 本地缓存必须设
maximumSize - 避免缓存大对象(如图片、长文本)
九、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!