Spring Boot 的 3 种二级缓存落地方式

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

此时,二级缓存架构应运而生,本文将介绍在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 添加依赖

xml 复制代码
<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 配置二级缓存管理器

typescript 复制代码
@Configuration
@EnableCaching
public class CacheConfig {

    @Value("${spring.application.name:app}")
    private String appName;
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 创建Redis缓存管理器
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
                .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);
                }
                
                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 使用缓存注解

kotlin 复制代码
@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的发布订阅机制实现:

typescript 复制代码
@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 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);
    }
    
    // 缓存变更消息发布器
    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 定义缓存接口

csharp 复制代码
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 实现本地缓存

typescript 复制代码
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分布式缓存

typescript 复制代码
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 实现二级缓存

java 复制代码
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 缓存事件发布和订阅

typescript 复制代码
@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 = (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 缓存管理器

swift 复制代码
@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 使用示例

kotlin 复制代码
@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 添加依赖

xml 复制代码
<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

yaml 复制代码
# 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:

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

4.2.3 使用注解方式

kotlin 复制代码
@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方式

kotlin 复制代码
@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<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 高级特性:自动刷新和异步加载

typescript 复制代码
@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 缓存统计与监控

less 复制代码
@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 适用场景

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

五、总结

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

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

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

相关推荐
都叫我大帅哥8 分钟前
背压(Backpressure):响应式编程的“流量控制艺术”
java·flux
郝同学的测开笔记10 分钟前
云原生探索系列(十九):Go 语言 context.Context
后端·云原生·go
浮游本尊15 分钟前
Java学习第5天 - 输入输出与字符串处理
java
阿杰学编程17 分钟前
Go 语言中的条件判断和for 循环
java·数据库·golang
小小琪_Bmob后端云28 分钟前
引流之评论区截流实验
前端·后端·产品
考虑考虑30 分钟前
JDK9中的takeWhile
java·后端·java ee
boy快快长大38 分钟前
【CompletableFuture】常用方法(三)
java
掘金一周1 小时前
数据脱敏的这6种方案,真香!| 掘金一周 5.29
前端·人工智能·后端
无名的人20251 小时前
Trae:重新定义效率边界 - 一款让工作流「自动进化」的智能工作台
后端
努力学习的明1 小时前
MQ解决高并发下订单问题,实现流量削峰
开发语言·后端·并发·mq