开发者

SpringBoot中二级缓存实现方案总结

开发者 https://www.devze.com 2025-06-28 13:15 出处:网络 作者: 风象南
目录一、二级缓存概述1.1 什么是二级缓存1.2 为什么需要二级缓存二、Spring Cache + Redis方案2.1 基本原理2.2 实现步骤2.3 优缺点分析2.4 适用场景三、自定义二级缓存框架3.1 基本原理3.2 实现步骤3.3 优缺点分析3.
目录
  • 一、二级缓存概述
    • 1.1 什么是二级缓存
    • 1.2 为什么需要二级缓存
  • 二、Spring Cache + Redis方案
    • 2.1 基本原理
    • 2.2 实现步骤
    • 2.3 优缺点分析
    • 2.4 适用场景
  • 三、自定义二级缓存框架
    • 3.1 基本原理
    • 3.2 实现步骤
    • 3.3 优缺点分析
    • 3.4 适用场景
  • 四、JetCache框架方案
    • 4.1 基本原理
    • 4.2 实现步骤
    • 4.3 优缺点分析
    • 4.4 适用场景
  • 五、总结

    在高并发系统设计中,缓存是提升性能的关键策略之一。随着业务的发展,单一的缓存方案往往无法同时兼顾性能、可靠性和一致性等多方面需求。

    此时,二级缓存架构应运而生,本文将介绍在Spring Boot中实现二级缓存的三种方案。

    一、二级缓存概述

    1.1 什么是二级缓存

    二级缓存是一种多层次的缓存架构,通常由以下两个层次组成:

    • 一级缓存(本地缓存):直接在应用服务器内存中,访问速度极快,但容量有限且在分布式环境下无法共享
    • 二级缓存(分布式缓存):独立的缓存服务,如Redis或Memcached,可被多个应用实例共享,容量更大

    二级缓存的工作流程通常是:先查询本地缓存,若未命中则查询分布式缓存,仍未命中才访问数据库,并将结果回填到各级缓存中。

    1.2 为什么需要二级缓存

    单一缓存方案存在明显局限性:

    • 仅使用本地缓存:无法在分布式环境下保持数据一致性,每个实例都需要从数据库加载数据
    • 仅使用分布式缓存:每次访问都需要网络IO,无法发挥本地缓存的性能优势

    二级缓存结合了两者优势:

    • 利用本地缓存的高性能,大幅减少网络IO
    • 通过分布式缓存保证数据一致性
    • 减轻数据库压力,提高系统整体吞吐量
    • 更好的故障隔离,即使分布式缓存不可用,本地缓存仍可提供部分服务

    二、Spring Cache + Redis方案

    2.1 基本原理

    该方案利用Spring Cache提供的缓存抽象,配合Caffeine(本地缓存)和Redis(分布式缓存)实现二级缓存。

    Spring Cache提供了统一的缓存操作接口,可以通过简单的注解实现缓存功能。

    2.2 实现步骤

    2.2.1 添加依赖

    <dependencies>
        <!-- Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- 缓存支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        
        <!-- Redis支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <!-- Caffeine本地缓存 -->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>
        
        <!-- 序列化支持 -->
        <dependency>
            <groupId>com.fasterXML.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
    </dependencies>

    2.2.2 配置二级缓存管理器

    @Configuration
    @EnableCaching
    public class CacheConfig {
    
        @Value("${spring.application.name:app}")
        private String appName;
        
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
            // 创建Redis缓存管理器
            RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionandroidFactory)
                    .cacheDefaults(getRedisCacheConfigurationWithTtl(3600)) // 默认1小时过期
                    .withCacheConfiguration("userCache", getRedisCacheConfigurationWithTtl(1800)) // 用户缓存30分钟
                    .withCacheConfiguration("productCache", getRedisCacheConfigurationWithTtl(7200)) // 产品缓存2小时
                    .build();
            
            // 创建Caffeine缓存管理器
            CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
            caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
                    .initialCapacity(100) // 初始容量
                    .maximumSize(1000) // 最大容量
                    .expireAfterWrite(5, TimeUnit.MINUTES) // 写入后5分钟过期
                    .recordStats()); // 开启统计
            
            // 创建二级缓存管理器
            return new LayeringCacheManager(caffeineCacheManager, redisCacheManager);
        }
        
        private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(long seconds) {
            return RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofSeconds(seconds))
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2jsonRedisSerializer()))
                    .disableCachingNullValues()
                    .computePrefixWith(cacheName -> appName + ":" + cacheName + ":");
        }
        
        // 二级缓存管理器实现
        public static class LayeringCacheManager implements CacheManager {
            
            private final CacheManager localCacheManager;
            private final CacheManager remoteCacheManager;
            private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>();
            
            public LayeringCacheManager(CacheManager localCacheManager, CacheManager remoteCacheManager) {
                this.localCacheManager = localCacheManager;
                this.remoteCacheManager = remoteCacheManager;
            }
            
            @Override
            public Cache getCache(String name) {
                return cacheMap.computeIfAbsent(name, cacheName -> {
                    Cache localCache = localCacheManager.getCache(cacheName);
                    Cache remoteCache = remoteCacheManager.getCache(cacheName);
                    return new LayeringCache(localCache, remoteCache);
                });
            }
            
            @Override
            public Collection<String> getCacheNames() {
                Set<String> names = new LinkedHashSet<>();
                names.addAll(localCacheManager.getCacheNames());
                names.addAll(remoteCacheManager.getCacheNames());
                return names;
            }
            
            // 二级缓存实现
            static class LayeringCache implements Cache {
                private final Cache localCache;
                private final Cache remoteCache;
                
                public LayeringCache(Cache localCache, Cache remoteCache) {
                    this.localCache = localCache;
                    this.remoteCache = remoteCache;
                }
                
                @Override
                public String getName() {
                    return localCache.getName();
                }
                
                @Override
                public Object getNativeCache() {
                    return this;
                }
                
                @Override
                public ValueWrapper get(Object key) {
                    // 先查本地缓存
                    ValueWrapper wrapper = localCache.get(key);
                    if (wrapper != null) {
                        return wrapper;
                    }
                    
                    // 本地未命中,查远程缓存
                    wrapper = remoteCache.get(key);
                    if (wrapper != null) {
                        Object value = wrapper.get();
                        // 回填本地缓存
                        localCache.put(key, value);
                    }
           http://www.devze.com         
                    return wrapper;
                }
                
                @Override
                public <T> T get(Object key, Class<T> type) {
                    // 先查本地缓存
                    T value = localCache.get(key, type);
                    if (value != null) {
                        return value;
                    }
                    
                    // 本地未命中,查远程缓存
                    value = remoteCache.get(key, type);
                    if (value != null) {
                        // 回填本地缓存
                        localCache.put(key, value);
                    }
                    
                    return value;
                }
                
                @Override
                public <T> T get(Object key, Callable<T> valueLoader) {
                    // 先查本地缓存
                    try {
                        T value = localCache.get(key, () -> {
                            // 本地未命中,查远程缓存
                            try {
                                return remoteCache.get(key, valueLoader);
                            } catch (Exception e) {
                                // 远程缓存未命中或异常,执行valueLoader加载数据
                                T newValue = valueLoader.call();
                                if (newValue != null) {
                                    remoteCache.put(key, newValue); // 填充远程缓存
                                }
                                return newValue;
                            }
                        });
                        return value;
                    } catch (Exception e) {
                        // 本地缓存异常,尝试直接读远程缓存
                        try {
                            return remoteCache.get(key, valueLoader);
                        } catch (Exception ex) {
                            if (ex instanceof RuntimeException) {
                                throw (RuntimeException) ex;
                            }
                            throw new IllegalStateException(ex);
                        }
                    }
                }
                
                @Override
                public void put(Object key, Object value) {
                    remoteCache.put(key, value);  // 先放入远程缓存
                    localCache.put(key, value);   // 再放入本地缓存
                }
                
                @Override
                public void evict(Object key) {
                    remoteCache.evict(key);  // 先清远程缓存
                    localCache.evict(key);   // 再清本地缓存
                }
                
                @Override
                public void clear() {
                    remoteCache.clear();  // 先清远程缓存
                    localCache.clear();   // 再清本地缓存
                }
            }
        }
    }

    2.2.3 使用缓存注解

    @Service
    public class UserServiceImpl implements UserService {
        
        @Autowired
        private UserRepository userRepository;
        
        @Override
        @Cacheable(cacheNames = "userCache", key = "#id")
        public User getUserById(Long id) {
            return userRepository.findById(id).orElse(null);
        }
        
        @Override
        @CachePut(cacheNames = "userCache", key = "#user.id")
        public User saveUser(User user) {
            return userRepository.save(user);
        }
        
        @Override
        @CacheEvict(cacheNames = "userCache", key = "#id")
        public void deleteUser(Long id) {
            userRepository.deleteById(id);
        }
    }

    2.2.4 缓存同步问题

    在分布式环境下,需要保证缓存一致性。我们可以通过Redis的发布订阅机制实现:

    @Configuration
    public class CacheEvictionConfig {
    
        @Bean
        public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
            RedisMessageListenerContainer container = new RedisMessageListenerContainer();
            container.setConnectionFactory(connectionFactory);
            return container;
        }
        
        @Bean
        public RedisCacheMessageListener redisCacheMessageListener(RedisMessageListenerContainer listenerContainer, 
                                                                  CacheManager cacheManager) {
            return new RedisCacheMessageListener(listenerContainer, cacheManager);
        }
        
        // 缓存消息监听器
        public static class RedisCacheMessageListener {
            
            private static final String CACHE_CHANGE_TOPIC = "cache:changes";
            
            private final CacheManager cacheManager;
            
            public RedisCacheMessageListenephpr(RedisMessageListenerContainer listenerContainer, CacheManager cacheManager) {
                this.cacheManager = cacheManager;
                listenerContainer.addMessageListener((message, pattern) -> {
                    String body = new String(message.getBody());
                    CacheChangeMessage cacheMessage = JSON.parseobject(body, CacheChangeMessage.class);
                    
                    // 清除本地缓存
                    Cache cache = cacheManager.getCache(cacheMessage.getCacheName());
                    if (cache != null) {
                        if (cacheMessage.getKey() != null) {
                            cache.evict(cacheMessage.getKey());
                        } else {
                            cache.clear();
                        }
                    }
                }, new ChannelTopic(CACHE_CHANGE_TOPIC));
            }
        }
        
        @Bean
        public CacheChangePublisher cacheChangePublisher(RedisTemplate<String, String> redisTemplate) {
            return new CacheChangePublisher(redisTemplate);
        }
        
        // 缓存变更消息发布器
        public static class CacheChangePublisher {
            
            private static final String CACHE_CHANGE_TOPIC = "cache:changes";
            
            private final RedisTemplate<String, String> redisTemplate;
            
            public CacheChangePublisher(RedisTemplate<String, String> redisTemplate) {
                this.redisTemplate = redisTemplate;
            }
            
            public void publishCacheEvict(String cacheName, Object key) {
                CacheChangeMessage message = new CacheChangeMessage(cacheName, key);
                redisTemplate.convertAndSend(CACHE_CHANGE_TOPIC, JSON.toJSONString(message));
            }
            
            public void publishCacheClear(String cacheName) {
                CacheChangeMessage message = new CacheChangeMessage(cacheName, null);
                redisTemplate.convertAndSend(CACHE_CHANGE_TOPIC, JSON.toJSONString(message));
            }
        }
        
        // 缓存变更消息
        @Data
        @AllArgsConstructor
        public static class CacheChangeMessage {
            private String cacheName;
            private Object key;
        }
    }

    2.3 优缺点分析

    优点:

    1. 集成Spring Cache,使用简单,只需通过注解即可实现缓存功能

    2. 支持多种缓存实现的无缝切换

    3. 二级缓存逻辑集中管理,便于维护

    4. 支持缓存失效时间、容量等细粒度控制

    缺点:

    1. 需要自行实现二级缓存管理器,代码相对复杂

    2. 缓存同步需要额外实现,有一定复杂度

    3. 自定义缓存加载策略不够灵活

    4. 对于复杂查询场景支持有限

    2.4 适用场景

    • 需要快速集成缓存功能的项目
    • 使用Spring框架且熟悉Spring Cache机制的团队
    • 读多写少的业务场景
    • 对缓存一致性要求不是特别高的场景

    三、自定义二级缓存框架

    3.1 基本原理

    该方案通过自定义缓存框架,精确控制缓存的读写流程、失效策略和同步机制,实现更加贴合业务需求的二级缓存。

    这种方式虽然实现复杂度高,但提供了最大的灵活性和控制力。

    3.2 实现步骤

    3.2.1 定义缓存接口

    public interface Cache<K, V> {
        
        V get(K key);
        
        void put(K key, V value);
        
        void remove(K key);
        
        void clear();
        
        long size();
        
        boolean containsKey(K key);
    }
    
    public interface CacheLoader<K, V> {
        V load(K key);
    }

    3.2.2 实现本地缓存

    public class LocalCache<K, V> implements Cache<K, V> {
        
        private final com.github.benmanes.caffeine.cache.Cache<K, V> cache;
        
        public LocalCache(long maximumSize, long expireAfterWriteSeconds) {
            this.cache = Caffeine.newBuilder()
                    .maximumSize(maximumSize)
                    .expireAfterWrite(expireAfterWriteSeconds, TimeUnit.SECONDS)
                    .recordStats()
                    .build();
        }
        
        @Override
        public V get(K key) {
            return cache.getIfPresent(key);
        }
        
        @Override
        public void put(K key, V value) {
            if (value != null) {
                cache.put(key, value);
            }
        }
        
        @Override
        public void remove(K key) {
            cache.invalidate(key);
        }
        
        @Override
        public void clear() {
            cache.invalidateAll();
        }
        
        @Override
        public long size() {
            return cache.estimatedSize();
        }
        
        @Override
        public boolean containsKey(K key) {
            return cache.getIfPresent(key) != null;
        }
        
        public CacheStats stats() {
            return cache.stats();
        }
    }

    3.2.3 实现Redis分布式缓存

    public class RedisCache<K, V> implements Cache<K, V> {
        
        private final RedisTemplate<String, Object> redisTemplate;
        private final String cachePrefix;
        private final long expireSeconds;
        private final Class<V> valueType;
        
        public RedisCache(RedisTemplate<String, Object> redisTemplate, 
                          String cachePrefix, 
                          long expireSeconds,
                          Class<V> valueType) {
            this.redisTemplate = redisTemplate;
            this.cachePrefix = cachePrefix;
            this.expireSeconds = expireSeconds;
            this.valueType = valueType;
        }
        
        private String getCacheKey(K key) {
            return cachePrefix + ":" + key.toString();
        }
        
        @Override
        public V get(K key) {
            String cacheKey = getCacheKey(key);
            return (V) redisTemplate.opsForValue().get(cacheKey);
        }
        
        @Override
        public void put(K key, V value) {
            if (value != null) {
                String cacheKey = getCacheKey(key);
                redisTemplate.opsForValue().set(cacheKey, value, expireSeconds, TimeUnit.SECONDS);
            }
        }
        
        @Override
        public void remove(K key) {
            String cacheKey = getCacheKey(key);
            redisTemplate.delete(cacheKey);
        }
        
        @Override
        public void clear() {
            Set<String> keys = redisTemplate.keys(cachePrefix + ":*");
            if (keys != null && !keys.isEmpty()) {
                redisTemplate.delete(keys);
            }
        }
        
        @Override
        public long size() {
            Set<String> keys = redisTemplate.keys(cachePrefix + ":*");
            return keys != null ? keys.size() : 0;
        }
        
        @Override
        public boolean containsKey(K key) {
            String cacheKey = getCacheKey(key);
            return Boolean.TRUE.equals(redisTemplate.hasKey(cacheKey));
        }
    }

    3.2.4 实现二级缓存

    public class TwoLevelCache<K, V> implements Cache<K, V> {
        
        private final Cache<K, V> localCache;
        private final Cache<K, V> remoteCache;
        private final CacheLoader<K, V> cacheLoader;
        private final String cacheName;
        private final CacheEventPublisher eventPublisher;
        
        public TwoLevelCache(Cache<K, V> localCache, 
                             Cache<K, V> remoteCache, 
                             CacheLoader<K, V> cacheLoader,
                             String cacheName,
                             CacheEventPublisher eventPublisher) {
            this.localCache = localCache;
            this.remoteCache = remoteCache;
            this.cacheLoader = cacheLoader;
            this.cacheName = cacheName;
            this.eventPublisher = eventPublisher;
        }
        
        @Override
        public V get(K key) {
            // 先查本地缓存
            V value = localCache.get(key);
            if (value != null) {
                return value;
            }
            
            // 本地未命中,查远程缓存
            value = remoteCache.get(key);
            if (value != null) {
                // 回填本地缓存
                localCache.put(key, value);
                return value;
            }
            
            // 远程也未命中,加载数据
            if (cacheLoader != null) {
                value = cacheLoader.load(key);
                if (value != null) {
                    // 填充缓存
                    put(key, value);
                }
            }
            
            return value;
        }
        
        @Override
        public void put(K key, V value) {
            if (value != null) {
                // 先放入远程缓存,再放入本地缓存
                remoteCache.put(key, value);
                localCache.put(key, value);
            }
        }
        
        @Override
        public void remove(K key) {
            // 先清远程缓存,再清本地缓存
            remoteCache.remove(key);
            localCache.remove(key);
            
            // 发布缓存失效事件
            if (eventPublisher != null) {
                eventPublisher.publishCacheEvictEvent(cacheName, key);
            }
        }
        
        @Override
        public void clear() {
            // 先清远程缓存,再清本地缓存
            remoteCache.clear();
            localCache.clear();
            
            // 发布缓存清空事件
            if (eventPublisher != null) {
                eventPublisher.publishCacheClearEvent(cacheName);
            }
        }
        
        @Override
        public long size() {
            return remoteCache.size();
        }
        
        @Override
        public boolean containsKey(K key) {
            return localCache.containsKey(key) || remoteCache.containsKey(key);
        }
    }

    3.2.5 缓存事件发布和订阅

    @Component
    public class CacheEventPublisher {
        
        private final RedisTemplate<String, String> redisTemplate;
        private static final String CACHE_EVICT_TOPIC = "cache:evict";
        private static final String CACHE_CLEAR_TOPIC = "cache:clear";
        
        public CacheEventPublisher(RedisTemplate<String, String> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
        
        public void publishCacheEvictEvent(String cacheName, Object key) {
            Map<String, Object> message = new HashMap<>();
            message.put("cacheName", cacheName);
            message.put("key", key);
            redisTemplate.convertAndSend(CACHE_EVICT_TOPIC, JSON.toJSONString(message));
        }
        
        public void publishCacheClearEvent(String cacheName) {
            Map<String, Object> message = new HashMap<>();
            message.put("cacheName", cacheName);
            redisTemplate.convertAndSend(CACHE_CLEAR_TOPIC, JSON.toJSONString(message));
        }
    }
    
    @Component
    public class CacheEventListener {
        
        private final Map<String, TwoLevelCache<?, ?>> cacheMap;
        
        public CacheEventListener(RedisMessageListenerContainer listenerContainer, 
                                 Map<String, TwoLevelCache<?, ?>> cacheMap) {
            this.cacheMap = cacheMap;
            
            // 监听缓存失效事件
            MessageListener evictListener = (message, pattern) -> {
                String body = new String(message.getBody());
                Map<String, Object> map = JSON.parseObject(body, Map.class);
                String cacheName = (String) map.get("cacheName");
                Object key = map.get("key");
                
                TwoLevelCache<Object, Object> cache = (TwoLevelCache<Object, Object>) cacheMap.get(cacheName);
                if (cache != null) {
                    // 只清除本地缓存,远程缓存已经由发布者清除
                    ((LocalCache<Object, Object>)cache.getLocalCache()).remove(key);
                }
            };
            
            // 监听缓存清空事件
            MessageListener clearListener = http://www.devze.com(message, pattern) -> {
                String body = new String(message.getBody());
                Map<String, Object> map = JSON.parseObject(body, Map.class);
                String cacheName = (String) map.get("cacheName");
                
                TwoLevelCache<Object, Object> cache = (TwoLevelCache<Object, Object>) cacheMap.get(cacheName);
                if (cache != null) {
                    // 只清除本地缓存,远程缓存已经由发布者清除
                    ((LocalCache<Object, Object>)cache.getLocalCache()).clear();
                }
            };
            
            listenerContainer.addMessageListener(evictListener, new ChannelTopic("cache:evict"));
            listenerContainer.addMessageListener(clearListener, new ChannelTopic("cache:clear"));
        }
    }

    3.2.6 缓存管理器

    @Component
    public class TwoLevelCacheManager {
        
        private final RedisTemplate<String, Object> redisTemplate;
        private final CacheEventPublisher eventPublisher;
        private final Map<String, TwoLevelCache<?, ?>> cacheMap = new ConcurrentHashMap<>();
        
        public TwoLevelCacheManager(RedisTemplate<String, Object> redisTemplate, 
                                  CacheEventPublisher eventPublisher) {
            this.redisTemplate = redisTemplate;
            this.eventPublisher = eventPublisher;
        }
        
        public <K, V> TwoLevelCache<K, V> getCache(String cacheName, 
                                                 Class<V> valueType, 
                                                 CacheLoader<K, V> cacheLoader) {
            return getCache(cacheName, valueType, cacheLoader, 1000, 300, 3600);
        }
        
        @SuppressWarnings("unchecked")
        public <K, V> TwoLevelCache<K, V> getCache(String cacheName, 
                                                 Class<V> valueType, 
                                                 CacheLoader<K, V> cacheLoader,
                                                 long localMaxSize,
                                                 long localExpireSeconds,
                                                 long remoteExpireSeconds) {
            return (TwoLevelCache<K, V>) cacheMap.computeIfAbsent(cacheName, name -> {
                LocalCache<K, V> localCache = new LocalCache<>(localMaxSize, localExpireSeconds);
                RedisCache<K, V> remoteCache = new RedisCache<>(redisTemplate, name, remoteExpireSeconds, valueType);
                return new TwoLevelCache<>(localCache, remoteCache, cacheLoader, name, eventPublisher);
            });
        }
        
        public Map<String, TwoLevelCache<?, ?>> getCacheMap() {
            return Collections.unmodifiableMap(cacheMap);
        }
    }

    3.2.7 使用示例

    @Service
    public class UserServiceImpl implements UserService {
        
        @Autowired
        private UserRepository userRepository;
        
        @Autowired
        private TwoLevelCacheManager cacheManager;
        
        private TwoLevelCache<Long, User> userCache;
        
        @PostConstruct
        public void init() {
            userCache = cacheManager.getCache("user", User.class, this::loadUser, 1000, 300, 1800);
        }
        
        private User loadUser(Long id) {
            return userRepository.findById(id).orElse(null);
        }
        
        @Override
        public User getUserById(Long id) {
            return userCache.get(id);
        }
        
        @Override
        public User saveUser(User user) {
            User savedUser = userRepository.save(user);
            userCache.put(user.getId(), savedUser);
            return savedUser;
        }
        
        @Override
        public void deleteUser(Long id) {
            userRepository.deleteById(id);
            userCache.remove(id);
        }
    }

    3.3 优缺点分析

    优点:

    1. 完全自定义,可以根据业务需求灵活定制

    2. 精确控制缓存的加载、更新和失效逻辑

    3. 可以针对不同业务场景设计不同的缓存策略

    4. 缓存监控和统计更加全面

    缺点:

    1. 开发工作量大,需要实现所有缓存逻辑

    2. 代码复杂度高,需要考虑多种边界情况

    3. 不能直接利用Spring等框架提供的缓存抽象

    4. 维护成本较高

    3.4 适用场景

    • 对缓存性能和行为有精确控制需求的项目
    • 缓存策略复杂,标准框架难以满足的场景
    • 大型项目,有专人负责缓存框架开发和维护
    • 特殊业务需求,如精确的过期策略、按条件批量失效等

    四、JetCache框架方案

    4.1 基本原理

    JetCache是阿里开源的一款Java缓存抽象框架,原生支持二级缓存,并提供丰富的缓存功能,如缓存自动刷新、异步加载、分布式锁等。

    它在API设计上类似Spring Cache,但功能更加强大和灵活。

    4.2 实现步骤

    4.2.1 添加依赖

    <dependencies>
        <!-- Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- JetCache核心 -->
        <dependency>
            <groupId>com.alicp.jetcache</groupId>
            <artifactId>jetcache-starter-redis</artifactId>
            <version>2.7.1</version>
        </dependency>
    </dependencies>

    4.2.2 配置JetCache

    # application.yml
    jetcache:
      statIntervalMinutes: 15
      areaInCacheName: false
      hidePackages: com.example
      local:
        default:
          type: caffeine
          limit: 1000
          keyConvertor: fastjson
          expireAfterWriteInMillis: 300000  # 5分钟
      remote:
        default:
          type: redis
          keyConvertor: fastjson
          valueEncoder: java
          valueDecoder: java
          poolConfig:
            minIdle: 5
            maxIdle: 20
            maxTotal: 50
          host: ${redis.host}
          port: ${redis.port}
          expireAfterWriteInMillis: 1800000  # 30分钟

    在启动类上启用JetCache:

    @SpringBootApplication
    @EnableMethodCache(basePackages = "com.example")
    @EnableCreateCacheAnnotation
    public class Application {
        
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }

    4.2.3 使用注解方式

    @Service
    public class UserServiceImpl implements UserService {
        
        @Autowired
        private UserRepository userRepository;
        
        @Override
        @Cached(name = "user:", key = "#id", cacheType = CacheType.BOTH, expire = 1800)
        public User getUserById(Long id) {
            return userRepository.findById(id).orElse(null);
        }
        
        @Override
        @CacheUpdate(name = "user:", key = "#user.id", value = "#user")
        public User saveUser(User user) {
            return userRepository.save(user);
        }
        
        @Override
        @CacheInvalidate(name = "user:", key = "#id")
        public void deleteUser(Long id) {
            userRepository.deleteById(id);
        }
    }

    4.2.4 使用API方式

    @Service
    public class ProductServiceImpl implements ProductService {
        
        @Autowired
        private ProductRepository productRepository;
        
        @CreateCache(name = "product:", cacheType = CacheType.BOTH, expire = 3600, localExpire = 600)
        private Cache<Long, Product> productCache;
        
        @Override
        public Product getProductById(Long id) {
            // 自动加载功能,若缓存未命中,会执行lambda中的逻辑并将结果缓存
            return productCache.computeIfAbsent(id, this::loadProduct);
        }
        
        private Product loadProduct(Long id) {
            return productRepository.findById(id).orElse(null);
        }
        
        @Override
        public Product saveProduct(Product product) {
            Product savedProduct = productRepository.save(product);
            productCache.put(product.getId(), savedProduct);
            return savedProduct;
        }
        
        @Override
        public void deleteProduct(Long id) {
            productRepository.deleteById(id);
            productCache.remove(id);
        }
        
        // 批量操作
        @Override
        public List<Product> getProductsByIds(List<Long> ids) {
            Map<Long, Product> productMap = productCache.getAll(ids);
            List<Long> missedIds = ids.stream()
                    .filter(id -> !productMap.containsKey(id))
                    .collect(Collectors.toList());
            
            if (!missedIds.isEmpty()) {
                List<Product> missedProducts = productRepository.findAllById(missedIds);
                Map<ijHDl;Long, Product> missedProductMap = missedProducts.stream()
                        .collect(Collectors.toMap(Product::getId, p -> p));
                
                // 更新缓存
                productCache.putAll(missedProductMap);
                
                // 合并结果
                productMap.putAll(missedProductMap);
            }
            
            return ids.stream()
                    .map(productMap::get)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
        }
    }

    4.2.5 高级特性:自动刷新和异步加载

    @Service
    public class StockServiceImpl implements StockService {
        
        @Autowired
        private StockRepository stockRepository;
        
        // 自动刷新缓存,适合库存等频繁变化的数据
        @CreateCache(name = "stock:", 
                    cacheType = CacheType.BOTH, 
                    expire = 60,  // 1分钟后过期
                    localExpire = 10,  // 本地缓存10秒过期
                    refreshPolicy = RefreshPolicy.BACKGROUND,  // 后台刷新
                    penetrationProtect = true)  // 防止缓存穿透
        private Cache<Long, Stock> stockCache;
        
        @Override
        public Stock getStockById(Long productId) {
            return stockCache.computeIfAbsent(productId, this::loadStock);
        }
        
        private Stock loadStock(Long productId) {
            return stockRepository.findByProductId(productId).orElse(new Stock(productId, 0));
        }
        
        @Override
        public void updateStock(Long productId, int newQuantity) {
            stockRepository.updateQuantity(productId, newQuantity);
            stockCache.remove(productId);  // 直接失效缓存,后台自动刷新会加载新值
        }
    }

    4.2.6 缓存统计与监控

    @RestController
    @RequestMapping("/cache")
    public class CacheStatsController {
        
        @Autowired
        private CacheManager cacheManager;
        
        @GetMapping("/stats")
        public Map<String, CacheStats> getCacheStats() {
            Collection<Cache> caches = cacheManager.getCache(null);
            Map<String, CacheStats> statsMap = new HashMap<>();
            
            for (Cache cache : caches) {
                statsMap.put(cache.config().getName(), cache.getStatistics());
            }
            
            return statsMap;
        }
    }

    4.3 优缺点分析

    优点:

    1. 原生支持二级缓存,使用简单

    2. 提供注解和API两种使用方式,灵活性强

    3. 内置多种高级特性,如自动刷新、异步加载、分布式锁等

    4. 完善的缓存统计和监控支持

    5. 社区活跃,文档完善

    缺点:

    1. 增加项目依赖,引入第三方框架

    2. 配置相对复杂

    3. 学习成本相对较高

    4.4 适用场景

    • 需要开箱即用的二级缓存解决方案
    • 对缓存有丰富需求的项目,如自动刷新、异步加载等
    • 微服务架构,需要统一的缓存抽象

    五、总结

    选择合适的二级缓存方案需要考虑项目规模、团队技术栈、性能需求、功能需求等多方面因素。

    无论选择哪种方案,合理的缓存策略、完善的监控体系和优秀的运维实践都是构建高效缓存系统的关键。

    在实际应用中,缓存并非越多越好,应当根据业务特点和系统架构,在性能、复杂度和一致性之间找到平衡点。

    到此这篇关于SpringBoot中二级缓存实现方案总结的文章就介绍到这了,更多相关SpringBoot二级缓存内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号