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

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

今天我们要解决的,就是如何用Caffeine + Redis + 空值缓存构建一个安全高效的多级缓存体系。

核心思路是:

三级缓存:本地缓存→Redis缓存→数据库

空值缓存:防止缓存穿透

随机过期:防止缓存雪崩

热点保护:防止缓存击穿

  1. 本地缓存(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();
    }
}
  1. 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

多级缓存实现

  1. 缓存服务类
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();
}

空值缓存策略

  1. 布隆过滤器实现
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);
    }
}
  1. 空值缓存集成
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();
}

缓存更新策略

  1. 缓存同步
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);
    }
}
  1. 缓存预热
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);
    }
}

性能监控

  1. 缓存统计
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();
        });
    }
}
  1. 缓存健康检查
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

高可用:单级缓存故障不影响整体服务

安全防护:有效防止穿透、雪崩、击穿

配置优化建议

  1. 本地缓存优化
java 复制代码
cache:
  local:
    initial-capacity: 500
    max-size: 5000
    expire-after-write-minutes: 5
    expire-after-access-minutes: 2
    enable-statistics: true
  1. Redis缓存优化
java 复制代码
spring:
  redis:
    lettuce:
      pool:
        max-active: 50
        max-idle: 20
        min-idle: 10
        max-wait: 2000ms
    timeout: 1000ms

注意事项

在使用多级缓存时,需要注意以下几点:

数据一致性:确保各级缓存数据的一致性

内存管理:合理设置缓存大小,避免内存溢出

监控告警:建立缓存命中率、响应时间等监控指标

优雅降级:当缓存层不可用时,能平稳降级到数据库

安全考虑:敏感数据的缓存策略需要特别注意

相关推荐
indexsunny2 小时前
互联网大厂Java面试实战:从Spring Boot到微服务架构的音视频场景解析
java·spring boot·spring cloud·mybatis·spring security·jwt·flyway
spring2997922 小时前
Spring Boot 整合 Druid 并开启监控
java·spring boot·后端
014-code2 小时前
kafka + springboot快速入门
java·spring boot·分布式·kafka
Fang fan2 小时前
高并发、分布式场景下的ID生成策略
数据库·redis·分布式·缓存
李白的粉2 小时前
基于springboot的教师工作量管理系统
java·spring boot·毕业设计·课程设计·教师工作量管理系统·源代码
小王不爱笑1322 小时前
深入浅出 Docker 核心知识点,解锁容器化技术精髓
java·spring boot·docker
一棵树73512 小时前
Springboot项目常用工具对比总结
java·spring boot·后端
秋知叶i3 小时前
【git命令】Git 删除远程分支保姆级教程(含缓存清理 + 本地残留绝杀)
git·elasticsearch·缓存
霖霖总总3 小时前
[Redis小技巧19]缓存雪崩深度解析:原理、防御策略与工程实践
redis·缓存