SpringBoot + 多级缓存(Caffeine + Redis + 空值缓存):防穿透、防雪崩、低延迟三合一
今天我们要解决的,就是如何用Caffeine + Redis + 空值缓存构建一个安全高效的多级缓存体系。
核心思路是:
三级缓存:本地缓存→Redis缓存→数据库
空值缓存:防止缓存穿透
随机过期:防止缓存雪崩
热点保护:防止缓存击穿
- 本地缓存(Caffeine)
java
@Configuration
publicclass LocalCacheConfig {
@Bean
public Cache<String, Object> caffeineCache() {
return Caffeine.newBuilder()
// 设置初始容量
.initialCapacity(100)
// 设置最大容量
.maximumSize(10000)
// 设置过期时间
.expireAfterWrite(Duration.ofMinutes(5))
// 设置访问过期时间
.expireAfterAccess(Duration.ofMinutes(2))
// 统计缓存命中率
.recordStats()
// 构建缓存实例
.build();
}
}
- Redis缓存配置
java
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
多级缓存实现
- 缓存服务类
java
@Service
@Slf4j
publicclass 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();
returnnull;
}
}
}
/**
* 缓存空值,防止穿透
*/
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);
}
privatestaticfinal Object NULL_VALUE = new Object();
}
空值缓存策略
- 布隆过滤器实现
java
@Component
publicclass BloomFilterCache {
privatefinal 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);
}
}
- 空值缓存集成
java
@Service
publicclass 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);
returnnull;
}
// 继续正常的多级缓存查询逻辑...
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;
} elseif (NULL_VALUE.equals(redisResult)) {
// Redis中存在空值标记
returnnull;
}
// 查询数据库
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);
}
privatestaticfinal Object NULL_VALUE = new Object();
}
缓存更新策略
- 缓存同步
java
@Service
@Transactional
publicclass 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);
}
}
- 缓存预热
java
@Component
publicclass 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);
}
}
性能监控
- 缓存统计
java
@Component
publicclass CacheMonitor {
privatefinal MeterRegistry meterRegistry;
privatefinal Counter hitCounter;
privatefinal Counter missCounter;
privatefinal 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();
});
}
}
- 缓存健康检查
java
@Component
publicclass 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) {
returnfalse;
}
}
private boolean checkRedisConnection() {
try {
redisTemplate.opsForValue().set("health_check", System.currentTimeMillis());
return redisTemplate.opsForValue().get("health_check") != null;
} catch (Exception e) {
returnfalse;
}
}
}
实际应用效果
通过多级缓存架构,我们可以实现:
高命中率:本地缓存命中率>90%,Redis命中率>95%
低延迟:99%请求响应时间<10ms
高可用:单级缓存故障不影响整体服务
安全防护:有效防止穿透、雪崩、击穿
配置优化建议
- 本地缓存优化
java
cache:
local:
initial-capacity: 500
max-size: 5000
expire-after-write-minutes: 5
expire-after-access-minutes: 2
enable-statistics: true
- Redis缓存优化
java
spring:
redis:
lettuce:
pool:
max-active: 50
max-idle: 20
min-idle: 10
max-wait: 2000ms
timeout: 1000ms
注意事项
在使用多级缓存时,需要注意以下几点:
数据一致性:确保各级缓存数据的一致性
内存管理:合理设置缓存大小,避免内存溢出
监控告警:建立缓存命中率、响应时间等监控指标
优雅降级:当缓存层不可用时,能平稳降级到数据库
安全考虑:敏感数据的缓存策略需要特别注意