最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot中二級(jí)緩存實(shí)現(xiàn)方案總結(jié)

 更新時(shí)間:2025年06月18日 08:08:32   作者:風(fēng)象南  
隨著業(yè)務(wù)的發(fā)展,單一的緩存方案往往無法同時(shí)兼顧性能、可靠性和一致性等多方面需求,此時(shí),二級(jí)緩存架構(gòu)應(yīng)運(yùn)而生,本文將介紹在Spring Boot中實(shí)現(xiàn)二級(jí)緩存的三種方案,大家可以根據(jù)需要進(jìn)行選擇

在高并發(fā)系統(tǒng)設(shè)計(jì)中,緩存是提升性能的關(guān)鍵策略之一。隨著業(yè)務(wù)的發(fā)展,單一的緩存方案往往無法同時(shí)兼顧性能、可靠性和一致性等多方面需求。

此時(shí),二級(jí)緩存架構(gòu)應(yīng)運(yùn)而生,本文將介紹在Spring Boot中實(shí)現(xiàn)二級(jí)緩存的三種方案。

一、二級(jí)緩存概述

1.1 什么是二級(jí)緩存

二級(jí)緩存是一種多層次的緩存架構(gòu),通常由以下兩個(gè)層次組成:

  • 一級(jí)緩存(本地緩存):直接在應(yīng)用服務(wù)器內(nèi)存中,訪問速度極快,但容量有限且在分布式環(huán)境下無法共享
  • 二級(jí)緩存(分布式緩存):獨(dú)立的緩存服務(wù),如Redis或Memcached,可被多個(gè)應(yīng)用實(shí)例共享,容量更大

二級(jí)緩存的工作流程通常是:先查詢本地緩存,若未命中則查詢分布式緩存,仍未命中才訪問數(shù)據(jù)庫(kù),并將結(jié)果回填到各級(jí)緩存中。

1.2 為什么需要二級(jí)緩存

單一緩存方案存在明顯局限性:

  • 僅使用本地緩存:無法在分布式環(huán)境下保持?jǐn)?shù)據(jù)一致性,每個(gè)實(shí)例都需要從數(shù)據(jù)庫(kù)加載數(shù)據(jù)
  • 僅使用分布式緩存:每次訪問都需要網(wǎng)絡(luò)IO,無法發(fā)揮本地緩存的性能優(yōu)勢(shì)

二級(jí)緩存結(jié)合了兩者優(yōu)勢(shì):

  • 利用本地緩存的高性能,大幅減少網(wǎng)絡(luò)IO
  • 通過分布式緩存保證數(shù)據(jù)一致性
  • 減輕數(shù)據(jù)庫(kù)壓力,提高系統(tǒng)整體吞吐量
  • 更好的故障隔離,即使分布式緩存不可用,本地緩存仍可提供部分服務(wù)

二、Spring Cache + Redis方案

2.1 基本原理

該方案利用Spring Cache提供的緩存抽象,配合Caffeine(本地緩存)和Redis(分布式緩存)實(shí)現(xiàn)二級(jí)緩存。

Spring Cache提供了統(tǒng)一的緩存操作接口,可以通過簡(jiǎn)單的注解實(shí)現(xiàn)緩存功能。

2.2 實(shí)現(xiàn)步驟

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 配置二級(jí)緩存管理器

@Configuration
@EnableCaching
public class CacheConfig {

    @Value("${spring.application.name:app}")
    private String appName;
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 創(chuàng)建Redis緩存管理器
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(getRedisCacheConfigurationWithTtl(3600)) // 默認(rèn)1小時(shí)過期
                .withCacheConfiguration("userCache", getRedisCacheConfigurationWithTtl(1800)) // 用戶緩存30分鐘
                .withCacheConfiguration("productCache", getRedisCacheConfigurationWithTtl(7200)) // 產(chǎn)品緩存2小時(shí)
                .build();
        
        // 創(chuàng)建Caffeine緩存管理器
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
                .initialCapacity(100) // 初始容量
                .maximumSize(1000) // 最大容量
                .expireAfterWrite(5, TimeUnit.MINUTES) // 寫入后5分鐘過期
                .recordStats()); // 開啟統(tǒng)計(jì)
        
        // 創(chuàng)建二級(jí)緩存管理器
        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 + ":");
    }
    
    // 二級(jí)緩存管理器實(shí)現(xiàn)
    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;
        }
        
        // 二級(jí)緩存實(shí)現(xiàn)
        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;
                }
                
                // 本地未命中,查遠(yuǎn)程緩存
                wrapper = remoteCache.get(key);
                if (wrapper != null) {
                    Object value = wrapper.get();
                    // 回填本地緩存
                    localCache.put(key, value);
                }
                
                return wrapper;
            }
            
            @Override
            public <T> T get(Object key, Class<T> type) {
                // 先查本地緩存
                T value = localCache.get(key, type);
                if (value != null) {
                    return value;
                }
                
                // 本地未命中,查遠(yuǎn)程緩存
                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, () -> {
                        // 本地未命中,查遠(yuǎn)程緩存
                        try {
                            return remoteCache.get(key, valueLoader);
                        } catch (Exception e) {
                            // 遠(yuǎn)程緩存未命中或異常,執(zhí)行valueLoader加載數(shù)據(jù)
                            T newValue = valueLoader.call();
                            if (newValue != null) {
                                remoteCache.put(key, newValue); // 填充遠(yuǎn)程緩存
                            }
                            return newValue;
                        }
                    });
                    return value;
                } catch (Exception e) {
                    // 本地緩存異常,嘗試直接讀遠(yuǎn)程緩存
                    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);  // 先放入遠(yuǎn)程緩存
                localCache.put(key, value);   // 再放入本地緩存
            }
            
            @Override
            public void evict(Object key) {
                remoteCache.evict(key);  // 先清遠(yuǎn)程緩存
                localCache.evict(key);   // 再清本地緩存
            }
            
            @Override
            public void clear() {
                remoteCache.clear();  // 先清遠(yuǎn)程緩存
                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 緩存同步問題

在分布式環(huán)境下,需要保證緩存一致性。我們可以通過Redis的發(fā)布訂閱機(jī)制實(shí)現(xiàn):

@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);
    }
    
    // 緩存消息監(jiān)聽器
    public static class RedisCacheMessageListener {
        
        private static final String CACHE_CHANGE_TOPIC = "cache:changes";
        
        private final CacheManager cacheManager;
        
        public RedisCacheMessageListener(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);
    }
    
    // 緩存變更消息發(fā)布器
    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 優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

1. 集成Spring Cache,使用簡(jiǎn)單,只需通過注解即可實(shí)現(xiàn)緩存功能

2. 支持多種緩存實(shí)現(xiàn)的無縫切換

3. 二級(jí)緩存邏輯集中管理,便于維護(hù)

4. 支持緩存失效時(shí)間、容量等細(xì)粒度控制

缺點(diǎn):

1. 需要自行實(shí)現(xiàn)二級(jí)緩存管理器,代碼相對(duì)復(fù)雜

2. 緩存同步需要額外實(shí)現(xiàn),有一定復(fù)雜度

3. 自定義緩存加載策略不夠靈活

4. 對(duì)于復(fù)雜查詢場(chǎng)景支持有限

2.4 適用場(chǎng)景

  • 需要快速集成緩存功能的項(xiàng)目
  • 使用Spring框架且熟悉Spring Cache機(jī)制的團(tuán)隊(duì)
  • 讀多寫少的業(yè)務(wù)場(chǎng)景
  • 對(duì)緩存一致性要求不是特別高的場(chǎng)景

三、自定義二級(jí)緩存框架

3.1 基本原理

該方案通過自定義緩存框架,精確控制緩存的讀寫流程、失效策略和同步機(jī)制,實(shí)現(xiàn)更加貼合業(yè)務(wù)需求的二級(jí)緩存。

這種方式雖然實(shí)現(xiàn)復(fù)雜度高,但提供了最大的靈活性和控制力。

3.2 實(shí)現(xiàn)步驟

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 實(shí)現(xiàn)本地緩存

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 實(shí)現(xiàn)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 實(shí)現(xiàn)二級(jí)緩存

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;
        }
        
        // 本地未命中,查遠(yuǎn)程緩存
        value = remoteCache.get(key);
        if (value != null) {
            // 回填本地緩存
            localCache.put(key, value);
            return value;
        }
        
        // 遠(yuǎn)程也未命中,加載數(shù)據(jù)
        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) {
            // 先放入遠(yuǎn)程緩存,再放入本地緩存
            remoteCache.put(key, value);
            localCache.put(key, value);
        }
    }
    
    @Override
    public void remove(K key) {
        // 先清遠(yuǎn)程緩存,再清本地緩存
        remoteCache.remove(key);
        localCache.remove(key);
        
        // 發(fā)布緩存失效事件
        if (eventPublisher != null) {
            eventPublisher.publishCacheEvictEvent(cacheName, key);
        }
    }
    
    @Override
    public void clear() {
        // 先清遠(yuǎn)程緩存,再清本地緩存
        remoteCache.clear();
        localCache.clear();
        
        // 發(fā)布緩存清空事件
        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 緩存事件發(fā)布和訂閱

@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;
        
        // 監(jiān)聽緩存失效事件
        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) {
                // 只清除本地緩存,遠(yuǎn)程緩存已經(jīng)由發(fā)布者清除
                ((LocalCache<Object, Object>)cache.getLocalCache()).remove(key);
            }
        };
        
        // 監(jiān)聽緩存清空事件
        MessageListener clearListener = (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) {
                // 只清除本地緩存,遠(yuǎn)程緩存已經(jīng)由發(fā)布者清除
                ((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 優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

1. 完全自定義,可以根據(jù)業(yè)務(wù)需求靈活定制

2. 精確控制緩存的加載、更新和失效邏輯

3. 可以針對(duì)不同業(yè)務(wù)場(chǎng)景設(shè)計(jì)不同的緩存策略

4. 緩存監(jiān)控和統(tǒng)計(jì)更加全面

缺點(diǎn):

1. 開發(fā)工作量大,需要實(shí)現(xiàn)所有緩存邏輯

2. 代碼復(fù)雜度高,需要考慮多種邊界情況

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

4. 維護(hù)成本較高

3.4 適用場(chǎng)景

  • 對(duì)緩存性能和行為有精確控制需求的項(xiàng)目
  • 緩存策略復(fù)雜,標(biāo)準(zhǔn)框架難以滿足的場(chǎng)景
  • 大型項(xiàng)目,有專人負(fù)責(zé)緩存框架開發(fā)和維護(hù)
  • 特殊業(yè)務(wù)需求,如精確的過期策略、按條件批量失效等

四、JetCache框架方案

4.1 基本原理

JetCache是阿里開源的一款Java緩存抽象框架,原生支持二級(jí)緩存,并提供豐富的緩存功能,如緩存自動(dòng)刷新、異步加載、分布式鎖等。

它在API設(shè)計(jì)上類似Spring Cache,但功能更加強(qiáng)大和靈活。

4.2 實(shí)現(xiàn)步驟

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分鐘

在啟動(dòng)類上啟用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) {
        // 自動(dòng)加載功能,若緩存未命中,會(huì)執(zhí)行l(wèi)ambda中的邏輯并將結(jié)果緩存
        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<Long, Product> missedProductMap = missedProducts.stream()
                    .collect(Collectors.toMap(Product::getId, p -> p));
            
            // 更新緩存
            productCache.putAll(missedProductMap);
            
            // 合并結(jié)果
            productMap.putAll(missedProductMap);
        }
        
        return ids.stream()
                .map(productMap::get)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }
}

4.2.5 高級(jí)特性:自動(dòng)刷新和異步加載

@Service
public class StockServiceImpl implements StockService {
    
    @Autowired
    private StockRepository stockRepository;
    
    // 自動(dòng)刷新緩存,適合庫(kù)存等頻繁變化的數(shù)據(jù)
    @CreateCache(name = "stock:", 
                cacheType = CacheType.BOTH, 
                expire = 60,  // 1分鐘后過期
                localExpire = 10,  // 本地緩存10秒過期
                refreshPolicy = RefreshPolicy.BACKGROUND,  // 后臺(tái)刷新
                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);  // 直接失效緩存,后臺(tái)自動(dòng)刷新會(huì)加載新值
    }
}

4.2.6 緩存統(tǒng)計(jì)與監(jiān)控

@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 優(yōu)缺點(diǎn)分析

優(yōu)點(diǎn):

1. 原生支持二級(jí)緩存,使用簡(jiǎn)單

2. 提供注解和API兩種使用方式,靈活性強(qiáng)

3. 內(nèi)置多種高級(jí)特性,如自動(dòng)刷新、異步加載、分布式鎖等

4. 完善的緩存統(tǒng)計(jì)和監(jiān)控支持

5. 社區(qū)活躍,文檔完善

缺點(diǎn):

1. 增加項(xiàng)目依賴,引入第三方框架

2. 配置相對(duì)復(fù)雜

3. 學(xué)習(xí)成本相對(duì)較高

4.4 適用場(chǎng)景

  • 需要開箱即用的二級(jí)緩存解決方案
  • 對(duì)緩存有豐富需求的項(xiàng)目,如自動(dòng)刷新、異步加載等
  • 微服務(wù)架構(gòu),需要統(tǒng)一的緩存抽象

五、總結(jié)

選擇合適的二級(jí)緩存方案需要考慮項(xiàng)目規(guī)模、團(tuán)隊(duì)技術(shù)棧、性能需求、功能需求等多方面因素。

無論選擇哪種方案,合理的緩存策略、完善的監(jiān)控體系和優(yōu)秀的運(yùn)維實(shí)踐都是構(gòu)建高效緩存系統(tǒng)的關(guān)鍵。

在實(shí)際應(yīng)用中,緩存并非越多越好,應(yīng)當(dāng)根據(jù)業(yè)務(wù)特點(diǎn)和系統(tǒng)架構(gòu),在性能、復(fù)雜度和一致性之間找到平衡點(diǎn)。

到此這篇關(guān)于SpringBoot中二級(jí)緩存實(shí)現(xiàn)方案總結(jié)的文章就介紹到這了,更多相關(guān)SpringBoot二級(jí)緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 面試總結(jié):秒殺設(shè)計(jì)、AQS 、synchronized相關(guān)問題

    面試總結(jié):秒殺設(shè)計(jì)、AQS 、synchronized相關(guān)問題

    Java語言的關(guān)鍵字,當(dāng)它用來修飾一個(gè)方法或者一個(gè)代碼塊的時(shí)候,能夠保證在同一時(shí)刻最多只有一個(gè)線程執(zhí)行該段代碼。本文給大家介紹java中 synchronized的用法,對(duì)本文感興趣的朋友一起看看吧
    2021-06-06
  • MyEclipse安裝JS代碼提示的教程(Spket插件)

    MyEclipse安裝JS代碼提示的教程(Spket插件)

    本篇文章主要介紹了MyEclipse安裝JS代碼提示的教程(Spket插件),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-12-12
  • java中Serializable接口作用詳解

    java中Serializable接口作用詳解

    這篇文章主要為大家詳細(xì)介紹了java中Serializable接口作用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • SpringBoot使用Hibernate攔截器實(shí)現(xiàn)時(shí)間自動(dòng)注入的操作代碼

    SpringBoot使用Hibernate攔截器實(shí)現(xiàn)時(shí)間自動(dòng)注入的操作代碼

    這篇文章主要介紹了SpringBoot使用Hibernate攔截器實(shí)現(xiàn)時(shí)間自動(dòng)注入的操作代碼,主要包括hibernate攔截器的相關(guān)知識(shí),結(jié)合實(shí)例代碼給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-10-10
  • MyBatis-Plus 動(dòng)態(tài)表名SQL解析器的實(shí)現(xiàn)

    MyBatis-Plus 動(dòng)態(tài)表名SQL解析器的實(shí)現(xiàn)

    這篇文章主要介紹了MyBatis-Plus 動(dòng)態(tài)表名SQL解析器的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 深入理解Java反射

    深入理解Java反射

    在理解反射原理之前先要搞清類型信息。接下來通過本文給大家介紹java反射的深入理解,非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起看下吧
    2016-07-07
  • idea如何在service窗口中顯示多個(gè)服務(wù)

    idea如何在service窗口中顯示多個(gè)服務(wù)

    這篇文章主要介紹了idea如何在service窗口中顯示多個(gè)服務(wù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • 通過實(shí)例了解JavaBean開發(fā)及使用過程解析

    通過實(shí)例了解JavaBean開發(fā)及使用過程解析

    這篇文章主要介紹了通過實(shí)例了解JavaBean開發(fā)及使用過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Java 帶參數(shù)與帶返回值的方法的定義和調(diào)用

    Java 帶參數(shù)與帶返回值的方法的定義和調(diào)用

    在java中,方法就是用來完成解決某件事情或?qū)崿F(xiàn)某個(gè)功能的辦法。方法實(shí)現(xiàn)的過程中,會(huì)包含很多條語句用于完成某些有意義的功能——通常是處理文本,控制輸入或計(jì)算數(shù)值,這篇文章我們來探究一下帶參數(shù)與帶返回值的方法的定義和調(diào)用
    2022-04-04
  • Java?CompletableFuture實(shí)現(xiàn)多線程異步編排

    Java?CompletableFuture實(shí)現(xiàn)多線程異步編排

    這篇文章主要為大家介紹了Java?CompletableFuture實(shí)現(xiàn)多線程異步編排,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09

最新評(píng)論

新民市| 贵德县| 永定县| 马鞍山市| 凤翔县| 武隆县| 临颍县| 萍乡市| 盐城市| 太仆寺旗| 安康市| 乐陵市| 五指山市| 夏津县| 峨山| 瓦房店市| 白朗县| 遂平县| 化隆| 宿州市| 兴义市| 涞水县| 上蔡县| 维西| 道孚县| 兴隆县| 淅川县| 贵定县| 菏泽市| 尖扎县| 嫩江县| 仙游县| 泗水县| 信宜市| 河池市| 吴桥县| SHOW| 兴和县| 卓资县| 西盟| 鞍山市|