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面试圣经

全部评论
秋招春招别错过!简历没拿得出手的实习项目?来找我包装专属大厂项目,保证不烂大街,很多同学已靠这上岸,进主页看简介了解。
点赞 回复 分享
发布于 08-27 17:30 江苏

相关推荐

08-26 22:06
东北大学 Java
20min&nbsp;实习怎么用redis+token实现登录的?Redis缓存token这种存储方式的弊端,存在什么安全隐患?这种方式的弊端后续怎么去解决?Redis缓存token业务层面会有哪些风险,业务层面的风险怎么解决?Redis高并发、低耗时的底层是因为什么机制?Redis主从同步的逻辑是什么,主从同步有哪几种方式,持久化的方式,最常用哪些方式?Redis支持事务吗,怎么支持?慢查询怎么定位和规避,在日常开发情况下,怎么做规避,有没有关于SQL的最佳实践、最佳原理。20min场景题在抖音里面有一个关注功能,设计关注跟取消关注功能,怎么去设计,包括底层的设计、存储设计。对于用户的规模不一样的情况(小博主、大博主),底层在设计的时候会有什么差异?一个网红博主,发了一条动态,怎么去发送给粉丝?上游怎么去消费发的这些消息?5min开放题未来职业规划+个人优势10+min手撕输出一个数组的全排列&nbsp;a&nbsp;b&nbsp;c&nbsp;-&gt;&nbsp;abc&nbsp;acb&nbsp;bac&nbsp;bca&nbsp;cab&nbsp;cba第二天挂基本全是场景题和设计方法,看似很开放,但还是要答出来面试官想听到的点,我感觉我说的挺对的,实际上可能最开始回答的方向就不对,讲了很多系统设计上的思考,忽视了业务方向的思考。难难难,实在是太难了,有一种有力没处使的感觉。已经换部门重新从一面开始了
求offer的花生米...:面字节太累了,剪映飞书全都是最后一轮挂了,心态都炸了
查看12道真题和解析
点赞 评论 收藏
分享
评论
点赞
3
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务