在高并发系统设计中,缓存是提升性能的关键策略之一。随着业务的发展,单一的缓存方案往往无法同时兼顾性能、可靠性和一致性等多方面需求。
此时,二级缓存架构应运而生,本文将介绍在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;@Beanpublic 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;}@Overridepublic Cache getCache(String name) {return cacheMap.computeIfAbsent(name, cacheName -> {Cache localCache = localCacheManager.getCache(cacheName);Cache remoteCache = remoteCacheManager.getCache(cacheName);return new LayeringCache(localCache, remoteCache);});}@Overridepublic 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;}@Overridepublic String getName() {return localCache.getName();}@Overridepublic Object getNativeCache() {return this;}@Overridepublic 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;}@Overridepublic <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;}@Overridepublic <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);}}}@Overridepublic void put(Object key, Object value) {remoteCache.put(key, value); // 先放入远程缓存localCache.put(key, value); // 再放入本地缓存}@Overridepublic void evict(Object key) {remoteCache.evict(key); // 先清远程缓存localCache.evict(key); // 再清本地缓存}@Overridepublic void clear() {remoteCache.clear(); // 先清远程缓存localCache.clear(); // 再清本地缓存}}}
}
2.2.3 使用缓存注解
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate 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 {@Beanpublic RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(connectionFactory);return container;}@Beanpublic 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));}}@Beanpublic 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@AllArgsConstructorpublic 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();}@Overridepublic V get(K key) {return cache.getIfPresent(key);}@Overridepublic void put(K key, V value) {if (value != null) {cache.put(key, value);}}@Overridepublic void remove(K key) {cache.invalidate(key);}@Overridepublic void clear() {cache.invalidateAll();}@Overridepublic long size() {return cache.estimatedSize();}@Overridepublic 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();}@Overridepublic V get(K key) {String cacheKey = getCacheKey(key);return (V) redisTemplate.opsForValue().get(cacheKey);}@Overridepublic void put(K key, V value) {if (value != null) {String cacheKey = getCacheKey(key);redisTemplate.opsForValue().set(cacheKey, value, expireSeconds, TimeUnit.SECONDS);}}@Overridepublic void remove(K key) {String cacheKey = getCacheKey(key);redisTemplate.delete(cacheKey);}@Overridepublic void clear() {Set<String> keys = redisTemplate.keys(cachePrefix + ":*");if (keys != null && !keys.isEmpty()) {redisTemplate.delete(keys);}}@Overridepublic long size() {Set<String> keys = redisTemplate.keys(cachePrefix + ":*");return keys != null ? keys.size() : 0;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic void put(K key, V value) {if (value != null) {// 先放入远程缓存,再放入本地缓存remoteCache.put(key, value);localCache.put(key, value);}}@Overridepublic void remove(K key) {// 先清远程缓存,再清本地缓存remoteCache.remove(key);localCache.remove(key);// 发布缓存失效事件if (eventPublisher != null) {eventPublisher.publishCacheEvictEvent(cacheName, key);}}@Overridepublic void clear() {// 先清远程缓存,再清本地缓存remoteCache.clear();localCache.clear();// 发布缓存清空事件if (eventPublisher != null) {eventPublisher.publishCacheClearEvent(cacheName);}}@Overridepublic long size() {return remoteCache.size();}@Overridepublic 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 {@Autowiredprivate UserRepository userRepository;@Autowiredprivate TwoLevelCacheManager cacheManager;private TwoLevelCache<Long, User> userCache;@PostConstructpublic void init() {userCache = cacheManager.getCache("user", User.class, this::loadUser, 1000, 300, 1800);}private User loadUser(Long id) {return userRepository.findById(id).orElse(null);}@Overridepublic User getUserById(Long id) {return userCache.get(id);}@Overridepublic User saveUser(User user) {User savedUser = userRepository.save(user);userCache.put(user.getId(), savedUser);return savedUser;}@Overridepublic 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 添加依赖
<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: 15areaInCacheName: falsehidePackages: com.examplelocal:default:type: caffeinelimit: 1000keyConvertor: fastjsonexpireAfterWriteInMillis: 300000 # 5分钟remote:default:type: rediskeyConvertor: fastjsonvalueEncoder: javavalueDecoder: javapoolConfig:minIdle: 5maxIdle: 20maxTotal: 50host: ${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 {@Autowiredprivate 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 {@Autowiredprivate ProductRepository productRepository;@CreateCache(name = "product:", cacheType = CacheType.BOTH, expire = 3600, localExpire = 600)private Cache<Long, Product> productCache;@Overridepublic Product getProductById(Long id) {// 自动加载功能,若缓存未命中,会执行lambda中的逻辑并将结果缓存return productCache.computeIfAbsent(id, this::loadProduct);}private Product loadProduct(Long id) {return productRepository.findById(id).orElse(null);}@Overridepublic Product saveProduct(Product product) {Product savedProduct = productRepository.save(product);productCache.put(product.getId(), savedProduct);return savedProduct;}@Overridepublic void deleteProduct(Long id) {productRepository.deleteById(id);productCache.remove(id);}// 批量操作@Overridepublic 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 {@Autowiredprivate StockRepository stockRepository;// 自动刷新缓存,适合库存等频繁变化的数据@CreateCache(name = "stock:", cacheType = CacheType.BOTH, expire = 60, // 1分钟后过期localExpire = 10, // 本地缓存10秒过期refreshPolicy = RefreshPolicy.BACKGROUND, // 后台刷新penetrationProtect = true) // 防止缓存穿透private Cache<Long, Stock> stockCache;@Overridepublic Stock getStockById(Long productId) {return stockCache.computeIfAbsent(productId, this::loadStock);}private Stock loadStock(Long productId) {return stockRepository.findByProductId(productId).orElse(new Stock(productId, 0));}@Overridepublic void updateStock(Long productId, int newQuantity) {stockRepository.updateQuantity(productId, newQuantity);stockCache.remove(productId); // 直接失效缓存,后台自动刷新会加载新值}
}
4.2.6 缓存统计与监控
@RestController
@RequestMapping("/cache")
public class CacheStatsController {@Autowiredprivate 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 适用场景
- 需要开箱即用的二级缓存解决方案
- 对缓存有丰富需求的项目,如自动刷新、异步加载等
- 微服务架构,需要统一的缓存抽象
五、总结
选择合适的二级缓存方案需要考虑项目规模、团队技术栈、性能需求、功能需求等多方面因素。
无论选择哪种方案,合理的缓存策略、完善的监控体系和优秀的运维实践都是构建高效缓存系统的关键。
在实际应用中,缓存并非越多越好,应当根据业务特点和系统架构,在性能、复杂度和一致性之间找到平衡点。