10.3 缓存架构设计
面试重要程度:⭐⭐⭐⭐⭐
常见提问方式:如何设计多级缓存体系?如何保证缓存一致性?缓存穿透、击穿、雪崩如何解决?
预计阅读时间:40分钟
🎯 多级缓存体系设计
缓存层次架构
/** * 多级缓存架构设计 */ @Component public class MultiLevelCacheArchitecture { /** * 缓存层级定义 */ public enum CacheLevel { L1_LOCAL_CACHE(1, "本地缓存", "JVM内存"), L2_DISTRIBUTED_CACHE(2, "分布式缓存", "Redis集群"), L3_DATABASE_CACHE(3, "数据库缓存", "MySQL查询缓存"), L4_CDN_CACHE(4, "CDN缓存", "边缘节点缓存"); private final int level; private final String name; private final String description; } /** * 多级缓存管理器 */ @Component public static class MultiLevelCacheManager { @Autowired private LocalCacheManager localCacheManager; @Autowired private DistributedCacheManager distributedCacheManager; /** * 多级缓存查询 */ public <T> T get(String key, Class<T> type, Supplier<T> dataLoader) { // L1: 本地缓存查询 T value = localCacheManager.get(key, type); if (value != null) { return value; } // L2: 分布式缓存查询 value = distributedCacheManager.get(key, type); if (value != null) { // 回写到本地缓存 localCacheManager.put(key, value, Duration.ofMinutes(5)); return value; } // L3: 数据源查询 value = dataLoader.get(); if (value != null) { // 写入各级缓存 distributedCacheManager.put(key, value, Duration.ofHours(1)); localCacheManager.put(key, value, Duration.ofMinutes(5)); } return value; } /** * 多级缓存更新 */ public <T> void put(String key, T value, Duration ttl) { localCacheManager.put(key, value, ttl); distributedCacheManager.put(key, value, ttl); } /** * 多级缓存删除 */ public void evict(String key) { localCacheManager.evict(key); distributedCacheManager.evict(key); } } /** * 本地缓存管理器 */ @Component public static class LocalCacheManager { private final Cache<String, Object> cache; public LocalCacheManager() { this.cache = Caffeine.newBuilder() .maximumSize(10000) // 最大条目数 .expireAfterWrite(Duration.ofMinutes(30)) // 写入后30分钟过期 .expireAfterAccess(Duration.ofMinutes(10)) // 访问后10分钟过期 .refreshAfterWrite(Duration.ofMinutes(5)) // 写入后5分钟刷新 .recordStats() // 记录统计信息 .build(); } @SuppressWarnings("unchecked") public <T> T get(String key, Class<T> type) { Object value = cache.getIfPresent(key); return value != null ? (T) value : null; } public <T> void put(String key, T value, Duration ttl) { cache.put(key, value); } public void evict(String key) { cache.invalidate(key); } /** * 获取缓存统计信息 */ public CacheStats getStats() { return cache.stats(); } } }
缓存预热与更新策略
/** * 缓存预热与更新策略 */ @Component public class CacheWarmupAndUpdateStrategy { /** * 缓存预热服务 */ @Component public static class CacheWarmupService { @Autowired private MultiLevelCacheManager cacheManager; /** * 应用启动时预热缓存 */ @EventListener(ApplicationReadyEvent.class) public void warmupOnStartup() { log.info("Starting cache warmup..."); CompletableFuture.allOf( CompletableFuture.runAsync(this::warmupHotUsers), CompletableFuture.runAsync(this::warmupHotProducts), CompletableFuture.runAsync(this::warmupSystemConfig) ).thenRun(() -> { log.info("Cache warmup completed"); }); } /** * 预热热点用户数据 */ private void warmupHotUsers() { try { List<Long> hotUserIds = userService.getHotUserIds(1000); hotUserIds.parallelStream().forEach(userId -> { try { String cacheKey = "user:profile:" + userId; UserProfile profile = userService.getUserProfile(userId); cacheManager.put(cacheKey, profile, Duration.ofHours(2)); } catch (Exception e) { log.warn("Failed to warmup user: {}", userId, e); } }); log.info("Warmed up {} hot users", hotUserIds.size()); } catch (Exception e) { log.error("Failed to warmup hot users", e); } } /** * 预热热点商品数据 */ private void warmupHotProducts() { try { List<Long> hotProductIds = productService.getHotProductIds(5000); // 分批处理,避免内存压力 int batchSize = 100; for (int i = 0; i < hotProductIds.size(); i += batchSize) { int endIndex = Math.min(i + batchSize, hotProductIds.size()); List<Long> batch = hotProductIds.subList(i, endIndex); batch.parallelStream().forEach(productId -> { try { String cacheKey = "product:detail:" + productId; ProductDetail detail = productService.getProductDetail(productId); cacheManager.put(cacheKey, detail, Duration.ofHours(6)); } catch (Exception e) { log.warn("Failed to warmup product: {}", productId, e); } }); // 避免过快的请求 Thread.sleep(100); } log.info("Warmed up {} hot products", hotProductIds.size()); } catch (Exception e) { log.error("Failed to warmup hot products", e); } } } }
🔒 缓存一致性保证
缓存一致性策略
/** * 缓存一致性保证机制 */ @Component public class CacheConsistencyGuarantee { /** * 旁路缓存模式实现 */ @Component public static class CacheAsidePattern { @Autowired private RedisTemplate<String, Object> redisTemplate; @Autowired private UserRepository userRepository; /** * 查询用户信息(Cache Aside模式) */ public UserInfo getUserInfo(Long userId) { String cacheKey = "user:info:" + userId; // 1. 先查缓存 UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get(cacheKey); if (userInfo != null) { return userInfo; } // 2. 缓存未命中,查数据库 userInfo = userRepository.findById(userId); if (userInfo != null) { // 3. 写入缓存 redisTemplate.opsForValue().set(cacheKey, userInfo, Duration.ofMinutes(30)); } return userInfo; } /** * 更新用户信息(Cache Aside模式) */ @Transactional public void updateUserInfo(UserInfo userInfo) { String cacheKey = "user:info:" + userInfo.getId(); try { // 1. 先更新数据库 userRepository.save(userInfo); // 2. 删除缓存(让下次查询时重新加载) redisTemplate.delete(cacheKey); } catch (Exception e) { log.error("Failed to update user info", e); throw e; } } } /** * 分布式锁保证一致性 */ @Component public static class DistributedLockConsistency { @Autowired private RedisTemplate<String, String> redisTemplate; /** * 使用分布式锁保证缓存更新一致性 */ public <T> T updateWithLock(String lockKey, String cacheKey, Supplier<T> dataLoader, Duration lockTimeout) { String lockValue = UUID.randomUUID().toString(); try { // 获取分布式锁 Boolean lockAcquired = redisTemplate.opsForValue() .setIfAbsent(lockKey, lockValue, lockTimeout); if (!lockAcquired) { // 获取锁失败,等待一段时间后重试 Thread.sleep(100); return getCachedValue(cacheKey); } // 双重检查,避免重复加载 T cachedValue = getCachedValue(cacheKey); if (cachedValue != null) { return cachedValue; } // 加载数据并更新缓存 T newValue = dataLoader.get(); if (newValue != null) { redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(newValue), Duration.ofMinutes(30)); } return newValue; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Interrupted while waiting for lock", e); } finally { // 释放锁 releaseLock(lockKey, lockValue); } } @SuppressWarnings("unchecked") private <T> T getCachedValue(String cacheKey) { String cachedJson = redisTemplate.opsForValue().get(cacheKey); if (cachedJson != null) { return (T) JSON.parseObject(cachedJson); } return null; } /** * 释放分布式锁 */ private void releaseLock(String lockKey, String lockValue) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(lockKey), lockValue); } } }
🛡️ 缓存问题解决方案
缓存穿透、击穿、雪崩解决方案
/** * 缓存问题解决方案 */ @Component public class CacheProblemSolutions { /** * 缓存穿透解决方案 */ @Component public static class CachePenetrationSolution { @Autowired private RedisTemplate<String, Object> redisTemplate; @Autowired private BloomFilter<String> bloomFilter; /** * 使用布隆过滤器防止缓存穿透 */ public <T> T getWithBloomFilter(String key, Class<T> type, Supplier<T> dataLoader) { // 1. 布隆过滤器检查 if (!bloomFilter.mightContain(key)) { log.info("Bloom filter rejected key: {}", key); return null; } // 2. 查询缓存 T cachedValue = getCachedValue(key, type); if (cachedValue != null) { return cachedValue; } // 3. 查询数据库 T value = dataLoader.get(); if (value != null) { redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(30)); } else { // 缓存空值,防止穿透 redisTemplate.opsForValue().set(key, "NULL", Duration.ofMinutes(5)); } return value; } /** * 缓存空值防穿透 */ public <T> T getWithNullCache(String key, Class<T> type, Supplier<T> dataLoader) { Object cachedValue = redisTemplate.opsForValue().get(key); // 检查是否为空值缓存 if ("NULL".equals(cachedValue)) { return null; } if (cachedValue != null) { return (T) cachedValue; } // 查询数据库 T value = dataLoader.get(); if (value != null) { redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(30)); } else { // 缓存空值,较短的过期时间 redisTemplate.opsForValue().set(key, "NULL", Duration.ofMinutes(5)); } return value; } @SuppressWarnings("unchecked") private <T> T getCachedValue(String key, Class<T> type) { Object value = redisTemplate.opsForValue().get(key); return value != null ? (T) value : null; } } /** * 缓存击穿解决方案 */ @Component public static class CacheBreakdownSolution { @Autowired private RedisTemplate<String, Object> redisTemplate; /** * 使用分布式锁防止缓存击穿 */ public <T> T getWithDistributedLock(String key, Class<T> type, Supplier<T> dataLoader) { T cachedValue = getCachedValue(key, type); if (cachedValue != null) { return cachedValue; } String lockKey = "lock:" + key; String lockValue = UUID.randomUUID().toString(); try { // 获取分布式锁 Boolean lockAcquired = redisTemplate.opsForValue() .setIfAbsent(lockKey, lockValue, Duration.ofSeconds(30)); if (lockAcquired) { // 获取锁成功,双重检查 cachedValue = getCachedValue(key, type); if (cachedValue != null) { return cachedValue; } // 加载数据 T value = dataLoader.get(); if (value != null) { redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(30)); } return value; } else { // 获取锁失败,等待一段时间后重试 Thread.sleep(100); return getWithDistributedLock(key, type, dataLoader); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Interrupted while waiting for lock", e); } finally { // 释放锁 releaseLock(lockKey, lockValue); } } @SuppressWarnings("unchecked") private <T> T getCachedValue(String key, Class<T> type) { Object value = redisTemplate.opsForValue().get(key); return value != null ? (T) value : null; } private void releaseLock(String lockKey, String lockValue) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(lockKey), lockValue); } } /** * 缓存雪崩解决方案 */ @Component public static class CacheAvalancheSolution { @Autowired private RedisTemplate<String, Object> redisTemplate; private final Random random = new Random(); /** * 随机过期时间防雪崩 */ public <T> void setWithRandomExpire(String key, T value, Duration baseTtl) { // 在基础TTL上增加随机时间(0-30分钟) long randomSeconds = random.nextInt(1800); Duration actualTtl = baseTtl.plusSeconds(randomSeconds); redisTemplate.opsForValue().set(key, value, actualTtl); } /** * 熔断器防雪崩 */ public <T> T getWithCircuitBreaker(String key, Class<T> type, Supplier<T> dataLoader) { // 检查熔断器状态 if (circuitBreaker.isOpen()) { // 熔断器开启,返回降级数据 return getFallbackData(key, type); } try { T value = getCachedValue(key, type); if (value != null) { circuitBreaker.recordSuccess(); return value; } // 缓存未命中,查询数据库 value = dataLoader.get(); if (value != null) { setWithRandomExpire(key, value, Duration.ofMinutes(30)); } circuitBreaker.recordSuccess(); return value; } catch (Exception e) { circuitBreaker.recordFailure(); return getFallbackData(key, type); } } @SuppressWarnings("unchecked") private <T> T getCachedValue(String key, Class<T> type) { Object value = redisTemplate.opsForValue().get(key); return value != null ? (T) value : null; } private <T> T getFallbackData(String key, Class<T> type) { // 返回降级数据或默认值 return null; } } }
💡 面试回答要点
标准回答模板
第一部分:多级缓存设计
"多级缓存体系包括: 1. L1本地缓存:JVM内存,访问最快,容量有限 2. L2分布式缓存:Redis集群,容量大,网络延迟 3. L3数据库缓存:MySQL查询缓存,减少磁盘IO 4. L4CDN缓存:边缘节点,就近访问 查询顺序:L1→L2→L3→数据库,命中后回写上级缓存。"
第二部分:缓存一致性保证
"缓存一致性策略: 1. Cache Aside:先更新数据库,再删除缓存 2. Write Through:同时更新数据库和缓存 3. Write Behind:先更新缓存,异步更新数据库 4. 分布式锁:保证更新操作的原子性 推荐Cache Aside模式,简单可靠。"
第三部分:缓存问题解决
"三大缓存问题解决方案: 1. 缓存穿透:布隆过滤器 + 空值缓存 2. 缓存击穿:分布式锁 + 双重检查 3. 缓存雪崩:随机过期时间 + 熔断降级 关键是预防为主,监控告警为辅。"
核心要点总结:
- ✅ 掌握多级缓存体系的设计原理和实现方法
- ✅ 理解缓存一致性的保证机制和策略选择
- ✅ 具备缓存常见问题的分析和解决能力
- ✅ 了解缓存性能优化和监控方案
Java面试圣经 文章被收录于专栏
Java面试圣经