在高并發系統設計中,緩存是提升性能的關鍵策略之一。隨著業務的發展,單一的緩存方案往往無法同時兼顧性能、可靠性和一致性等多方面需求。
此時,二級緩存架構應運而生,本文將介紹在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 適用場景
- 需要開箱即用的二級緩存解決方案
- 對緩存有豐富需求的項目,如自動刷新、異步加載等
- 微服務架構,需要統一的緩存抽象
五、總結
選擇合適的二級緩存方案需要考慮項目規模、團隊技術棧、性能需求、功能需求等多方面因素。
無論選擇哪種方案,合理的緩存策略、完善的監控體系和優秀的運維實踐都是構建高效緩存系統的關鍵。
在實際應用中,緩存并非越多越好,應當根據業務特點和系統架構,在性能、復雜度和一致性之間找到平衡點。