SpringBoot + 多级缓存(Caffeine + Redis + 空值缓存):防穿透、防雪崩、低延迟三合一

缓存的三大难题
在我们的日常开发工作中,经常会遇到这样的场景:
- 缓存穿透:大量请求查询不存在的数据,直接击穿到数据库
- 缓存雪崩:缓存同时失效,导致数据库瞬间压力过大
- 缓存击穿:热点数据缓存失效,大量请求涌向数据库
传统的单一缓存方案往往无法同时解决这三个问题。今天我们就来聊聊如何用SpringBoot + 多级缓存构建一个全方位的缓存防护体系。
为什么选择多级缓存
相比单一缓存方案,多级缓存有以下优势:
- 就近访问:本地缓存最快,Redis次之,数据库最慢
- 故障隔离:某一级缓存故障不影响其他层级
- 资源优化:合理分配不同层级的缓存资源
- 全面防护:多层防护,抵御各种缓存攻击
解决方案思路
今天我们要解决的,就是如何用Caffeine + Redis + 空值缓存构建一个安全高效的多级缓存体系。
核心思路是:
- 三级缓存:本地缓存→Redis缓存→数据库
- 空值缓存:防止缓存穿透
- 随机过期:防止缓存雪崩
- 热点保护:防止缓存击穿
缓存架构设计
1. 本地缓存(Caffeine)
@Configuration
public class LocalCacheConfig {
@Bean
public Cache<String, Object> caffeineCache() {
return Caffeine.newBuilder()
// 设置初始容量
.initialCapacity(100)
// 设置最大容量
.maximumSize(10000)
// 设置过期时间
.expireAfterWrite(Duration.ofMinutes(5))
// 设置访问过期时间
.expireAfterAccess(Duration.ofMinutes(2))
// 统计缓存命中率
.recordStats()
// 构建缓存实例
.build();
}
}
2. Redis缓存配置
spring:
redis:
host: localhost
port: 6379
timeout: 2000ms
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
# 自定义缓存配置
cache:
local:
enabled: true
initial-capacity: 1000
max-size: 10000
ttl-minutes: 5
redis:
enabled: true
default-ttl-minutes: 30
hot-data-ttl-minutes: 10
多级缓存实现
1. 缓存服务类
@Service
@Slf4j
public class MultiLevelCacheService {
@Autowired
private Cache<String, Object> localCache;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserService userService;
/**
* 多级缓存查询
*/
public User getUserById(Long userId) {
String cacheKey = buildCacheKey("user", userId);
// 1. 查询本地缓存
Object localResult = localCache.getIfPresent(cacheKey);
if (localResult != null) {
log.debug("本地缓存命中: {}", cacheKey);
return (User) localResult;
}
// 2. 查询Redis缓存
Object redisResult = redisTemplate.opsForValue().get(cacheKey);
if (redisResult != null) {
log.debug("Redis缓存命中: {}", cacheKey);
// 同步到本地缓存
localCache.put(cacheKey, redisResult);
return (User) redisResult;
}
// 3. 缓存未命中,查询数据库
User user = queryUserFromDB(userId);
// 4. 将结果存入各级缓存
if (user != null) {
// 存入Redis
redisTemplate.opsForValue().set(cacheKey, user,
generateRandomTTL(25, 35), TimeUnit.MINUTES);
// 存入本地缓存
localCache.put(cacheKey, user);
} else {
// 空值缓存,防止穿透
cacheNullValue(cacheKey);
}
return user;
}
/**
* 查询用户数据(带空值缓存)
*/
private User queryUserFromDB(Long userId) {
String lockKey = "lock:user:query:" + userId;
// 使用分布式锁防止缓存击穿
Boolean acquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofSeconds(10));
if (Boolean.TRUE.equals(acquired)) {
try {
return userService.selectById(userId);
} finally {
redisTemplate.delete(lockKey);
}
} else {
// 获取锁失败,等待一段时间后重试
try {
Thread.sleep(100);
return queryUserFromDB(userId); // 递归重试
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
}
/**
* 缓存空值,防止穿透
*/
private void cacheNullValue(String cacheKey) {
// 设置较短的过期时间
redisTemplate.opsForValue().set(cacheKey, NULL_VALUE,
Duration.ofMinutes(5));
// 本地缓存也记录空值(短暂时间)
localCache.put(cacheKey, NULL_VALUE);
}
/**
* 生成随机TTL,防止缓存雪崩
*/
private Duration generateRandomTTL(int minMinutes, int maxMinutes) {
Random random = new Random();
int randomMinutes = minMinutes + random.nextInt(maxMinutes - minMinutes + 1);
return Duration.ofMinutes(randomMinutes);
}
private String buildCacheKey(String prefix, Object key) {
return String.format("%s:%s", prefix, key);
}
private static final Object NULL_VALUE = new Object();
}
空值缓存策略
1. 布隆过滤器实现
@Component
public class BloomFilterCache {
private final BloomFilter<String> bloomFilter;
public BloomFilterCache() {
// 预期数据量100万,误判率0.01
this.bloomFilter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
1000000,
0.01
);
}
/**
* 判断key是否存在(可能存在或一定不存在)
*/
public boolean mightContain(String key) {
return bloomFilter.mightContain(key);
}
/**
* 添加key到过滤器
*/
public void put(String key) {
bloomFilter.put(key);
}
/**
* 批量添加
*/
public void putAll(Collection<String> keys) {
keys.forEach(this::put);
}
}
2. 空值缓存集成
@Service
public class EnhancedCacheService {
@Autowired
private BloomFilterCache bloomFilter;
@Autowired
private Cache<String, Object> localCache;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public User getUserById(Long userId) {
String cacheKey = buildCacheKey("user", userId);
// 使用布隆过滤器快速判断
if (!bloomFilter.mightContain(cacheKey)) {
log.debug("布隆过滤器判断数据不存在: {}", cacheKey);
return null;
}
// 继续正常的多级缓存查询逻辑...
return queryFromMultiLevelCache(cacheKey, userId);
}
private User queryFromMultiLevelCache(String cacheKey, Long userId) {
// 本地缓存查询
Object localResult = localCache.getIfPresent(cacheKey);
if (localResult != null && !NULL_VALUE.equals(localResult)) {
return (User) localResult;
}
// Redis缓存查询
Object redisResult = redisTemplate.opsForValue().get(cacheKey);
if (redisResult != null && !NULL_VALUE.equals(redisResult)) {
localCache.put(cacheKey, redisResult);
return (User) redisResult;
} else if (NULL_VALUE.equals(redisResult)) {
// Redis中存在空值标记
return null;
}
// 查询数据库
User user = queryUserFromDB(userId);
if (user != null) {
// 数据存在,更新布隆过滤器
bloomFilter.put(cacheKey);
// 设置到各级缓存
setToCache(cacheKey, user);
} else {
// 数据不存在,设置空值缓存
setNullCache(cacheKey);
}
return user;
}
private void setToCache(String cacheKey, User user) {
// 随机TTL防止雪崩
Duration ttl = generateRandomTTL(25, 35);
redisTemplate.opsForValue().set(cacheKey, user, ttl);
localCache.put(cacheKey, user);
}
private void setNullCache(String cacheKey) {
// 空值使用较短的固定TTL
redisTemplate.opsForValue().set(cacheKey, NULL_VALUE, Duration.ofMinutes(5));
localCache.put(cacheKey, NULL_VALUE);
}
private static final Object NULL_VALUE = new Object();
}
缓存更新策略
1. 缓存同步
@Service
@Transactional
public class UserService {
@Autowired
private EnhancedCacheService cacheService;
@Autowired
private UserMapper userMapper;
/**
* 更新用户信息,同步更新缓存
*/
@Transactional(rollbackFor = Exception.class)
public void updateUser(User user) {
// 更新数据库
userMapper.updateById(user);
// 清除各级缓存
String cacheKey = cacheService.buildCacheKey("user", user.getId());
// 删除Redis缓存
redisTemplate.delete(cacheKey);
// 删除本地缓存
localCache.invalidate(cacheKey);
log.info("用户缓存已清除: userId={}", user.getId());
}
/**
* 删除用户,清除缓存
*/
@Transactional(rollbackFor = Exception.class)
public void deleteUser(Long userId) {
// 删除数据库记录
userMapper.deleteById(userId);
// 清除缓存
String cacheKey = cacheService.buildCacheKey("user", userId);
redisTemplate.delete(cacheKey);
localCache.invalidate(cacheKey);
log.info("用户数据已删除并清除缓存: userId={}", userId);
}
}
2. 缓存预热
@Component
public class CacheWarmUpService {
@Autowired
private EnhancedCacheService cacheService;
@Autowired
private UserMapper userMapper;
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
log.info("开始缓存预热...");
// 分批加载热点数据
int pageSize = 1000;
int offset = 0;
while (true) {
List<User> users = userMapper.selectPage(
new Page<>(offset + 1, pageSize), null);
if (users.isEmpty()) {
break;
}
// 批量预热缓存
for (User user : users) {
cacheService.preheatCache(user);
}
offset += pageSize;
// 避免一次性加载过多数据
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
log.info("缓存预热完成,共预热 {} 条数据", offset);
}
}
性能监控
1. 缓存统计
@Component
public class CacheMonitor {
private final MeterRegistry meterRegistry;
private final Counter hitCounter;
private final Counter missCounter;
private final Timer cacheTimer;
public CacheMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.hitCounter = Counter.builder("cache.hits")
.description("缓存命中次数")
.register(meterRegistry);
this.missCounter = Counter.builder("cache.misses")
.description("缓存未命中次数")
.register(meterRegistry);
this.cacheTimer = Timer.builder("cache.access.time")
.description("缓存访问时间")
.register(meterRegistry);
}
public void recordHit(String cacheLevel) {
hitCounter.increment(Tags.of("level", cacheLevel));
}
public void recordMiss(String cacheLevel) {
missCounter.increment(Tags.of("level", cacheLevel));
}
public <T> T recordCacheOperation(String operation, Supplier<T> supplier) {
return cacheTimer.recordCallable(() -> {
return supplier.get();
});
}
}
2. 缓存健康检查
@Component
public class CacheHealthIndicator implements HealthIndicator {
@Autowired
private Cache<String, Object> localCache;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public Health health() {
try {
// 检查本地缓存
boolean localHealthy = checkLocalCache();
// 检查Redis连接
boolean redisHealthy = checkRedisConnection();
if (localHealthy && redisHealthy) {
return Health.up()
.withDetail("local_cache", "OK")
.withDetail("redis_cache", "OK")
.build();
} else {
return Health.down()
.withDetail("local_cache", localHealthy ? "OK" : "FAILED")
.withDetail("redis_cache", redisHealthy ? "OK" : "FAILED")
.build();
}
} catch (Exception e) {
return Health.down(e).build();
}
}
private boolean checkLocalCache() {
try {
localCache.put("health_check", System.currentTimeMillis());
return localCache.getIfPresent("health_check") != null;
} catch (Exception e) {
return false;
}
}
private boolean checkRedisConnection() {
try {
redisTemplate.opsForValue().set("health_check", System.currentTimeMillis());
return redisTemplate.opsForValue().get("health_check") != null;
} catch (Exception e) {
return false;
}
}
}
实际应用效果
通过多级缓存架构,我们可以实现:
- 高命中率:本地缓存命中率>90%,Redis命中率>95%
- 低延迟:99%请求响应时间<10ms
- 高可用:单级缓存故障不影响整体服务
- 安全防护:有效防止穿透、雪崩、击穿
配置优化建议
1. 本地缓存优化
cache:
local:
initial-capacity: 500
max-size: 5000
expire-after-write-minutes: 5
expire-after-access-minutes: 2
enable-statistics: true
2. Redis缓存优化
spring:
redis:
lettuce:
pool:
max-active: 50
max-idle: 20
min-idle: 10
max-wait: 2000ms
timeout: 1000ms
注意事项
在使用多级缓存时,需要注意以下几点:
- 数据一致性:确保各级缓存数据的一致性
- 内存管理:合理设置缓存大小,避免内存溢出
- 监控告警:建立缓存命中率、响应时间等监控指标
- 优雅降级:当缓存层不可用时,能平稳降级到数据库
- 安全考虑:敏感数据的缓存策略需要特别注意
总结
通过SpringBoot + 多级缓存的架构,我们可以构建一个全方位的缓存防护体系,既能提供高性能的缓存服务,又能有效防止各种缓存攻击。这种方案特别适合在高并发场景下使用,是保障系统稳定性的关键技术之一。
希望这篇文章对你有所帮助!如果你觉得有用,欢迎关注【服务端技术精选】公众号,获取更多后端技术干货。
标题:SpringBoot + 多级缓存(Caffeine + Redis + 空值缓存):防穿透、防雪崩、低延迟三合一
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/01/16/1769071756597.html
0 评论