Spring Boot 的 3 种二级缓存方式

3 阅读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 添加依赖 org.springframework.boot spring-boot-starter-web

<!-- 缓存支持 -->
<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>
运行项目并下载源码

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(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 使用缓存注解 @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 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 优缺点分析 优点:

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

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

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

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

缺点:

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

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

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

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

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 = (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 优缺点分析 优点:

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

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

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

缓存监控和统计更加全面

缺点:

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

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

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

维护成本较高

3.4 适用场景 对缓存性能和行为有精确控制需求的项目

缓存策略复杂,标准框架难以满足的场景

大型项目,有专人负责缓存框架开发和维护

特殊业务需求,如精确的过期策略、按条件批量失效等

四、JetCache框架方案 4.1 基本原理 JetCache是阿里开源的一款Java 缓存抽象框架,原生支持二级缓存,并提供丰富的缓存功能,如缓存自动刷新、异步加载、分布式锁等。它在API设计上类似Spring Cache,但功能更加强大和灵活。

4.2 实现步骤 4.2.1 添加依赖 org.springframework.boot spring-boot-starter-web

<!-- JetCache核心 -->
<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>2.7.1</version>
</dependency>
运行项目并下载源码

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.hostport:{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<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 优缺点分析 优点:

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

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

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

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

社区活跃,文档完善

缺点:

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

配置相对复杂

学习成本相对较高

4.4 适用场景 需要开箱即用的二级缓存解决方案

对缓存有丰富需求的项目,如自动刷新、异步加载等

微服务架构,需要统一的缓存抽象

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

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

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

原文链接:blog.csdn.net/xiangzhihon…