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

此时,二级缓存架构应运而生,本文将介绍在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 优缺点分析

优点:

  1. 集成Spring Cache,使用简单,只需通过注解即可实现缓存功能
  2. 支持多种缓存实现的无缝切换
  3. 二级缓存逻辑集中管理,便于维护
  4. 支持缓存失效时间、容量等细粒度控制

缺点:

  1. 需要自行实现二级缓存管理器,代码相对复杂
  2. 缓存同步需要额外实现,有一定复杂度
  3. 自定义缓存加载策略不够灵活
  4. 对于复杂查询场景支持有限

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

优点:

  1. 完全自定义,可以根据业务需求灵活定制
  2. 精确控制缓存的加载、更新和失效逻辑
  3. 可以针对不同业务场景设计不同的缓存策略
  4. 缓存监控和统计更加全面

缺点:

  1. 开发工作量大,需要实现所有缓存逻辑
  2. 代码复杂度高,需要考虑多种边界情况
  3. 不能直接利用Spring等框架提供的缓存抽象
  4. 维护成本较高

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

优点:

  1. 原生支持二级缓存,使用简单
  2. 提供注解和API两种使用方式,灵活性强
  3. 内置多种高级特性,如自动刷新、异步加载、分布式锁等
  4. 完善的缓存统计和监控支持
  5. 社区活跃,文档完善

缺点:

  1. 增加项目依赖,引入第三方框架
  2. 配置相对复杂
  3. 学习成本相对较高

4.4 适用场景

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

五、总结

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/84176.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/84176.shtml
英文地址,请注明出处:http://en.pswp.cn/web/84176.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Studio Profiler使用

一:memory 参考文献: AndroidStudio之内层泄漏工具Profiler使用指南_android studio profiler-CSDN博客

Zephyr boot

<!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Zephyr设备初始化机制交互式解析…

腾讯地图Web版解决热力图被轮廓覆盖的问题

前言 你好&#xff0c;我是喵喵侠。 还记得那天傍晚&#xff0c;我正对着电脑调试一个腾讯地图的热力图页面。项目是一个区域人流密度可视化模块&#xff0c;我加了一个淡蓝色的轮廓图层用于表示区域范围&#xff0c;热力图放在下面用于展示人流热度。效果一预览&#xff0c;…

【JVMGC垃圾回收场景总结】

文章目录 CMS在并发标记阶段&#xff0c;已经被标记的对象&#xff0c;又被新生代跨带引用&#xff0c;这时JVM会怎么处理?为什么 Minor GC 会发生 STW&#xff1f;有哪些对象是在栈上分配的&#xff1f;对象在 JVM 中的内存结构为什么需要对齐填充&#xff1f;JVM 对象分配空…

3_STM32开发板使用(STM32F103ZET6)

STM32开发板使用(STM32F103ZET6) 一、概述 当前所用开发板为正点原子精英板,MCU: STM32F103ZET6。一般而言,拿到板子之后先要对板子有基础的认识,包括对开发板上电开机、固件下载、调试方法这三个部分有基本的掌握。 二、系统开机 2.1 硬件连接 直接接电源线或Type-c线…

crackme012

crackme012 名称值软件名称attackiko.exe加壳方式无保护方式serial编译语言Delphi v1.0调试环境win10 64位使用工具x32dbg,PEid破解日期2025-06-18 -发现是 16位windows 程序环境还没搭好先留坑

CppCon 2016 学习:I Just Wanted a Random Integer

你想要一个随机整数&#xff0c;用于模拟随机大小的DNA读取片段&#xff08;reads&#xff09;&#xff0c;希望覆盖不同长度范围&#xff0c;也能测试边界情况。 代码部分是&#xff1a; #include <cstdlib> auto r std::rand() % 100;它生成一个0到99之间的随机整数&…

MySQL层级查询实战:无函数实现部门父路径

本次需要击毙的MySQL函数 函数主要用于获取部门的完整层级路径&#xff0c;方便在应用程序或SQL查询中直接调用&#xff0c;快速获得部门的上下级关系信息。执行该函数之后简单使用SQL可以实现数据库中部门名称查询。例如下面sql select name,GetDepartmentParentNames(du.de…

Python初学者教程:如何从文本中提取IP地址

Python初学者教程:如何从文本中提取IP地址 在网络安全和数据分析领域,经常需要从文本文件中提取IP地址。本文将引导您使用Python创建一个简单但实用的工具,用于从文本文件提取所有IP地址并将其保存到新文件中。即使您是编程新手,也可以跟随本教程学习Python的基础知识! …

【Redis】Redis核心探秘:数据类型的编码实现与高速访问之道

&#x1f4da;️前言 &#x1f31f;&#x1f31f;&#x1f31f;精彩导读 本次我们将全面剖析Redis的核心技术要点&#xff0c;包括其丰富的数据类型体系、高效的编码方式以及秒级响应的性能奥秘。对于渴望深入理解Redis底层机制的技术爱好者&#xff0c;这是一次难得的学习机会…

Halcon —— 多种二维码检测

工业视觉实战&#xff1a;Halcon多类型二维码识别技术详解 在工业自动化场景中&#xff0c;兼容多种二维码类型是提高生产线灵活性的关键。本文将深入解析Halcon实现Data Matrix、QR Code和PDF417三种主流二维码的兼容识别方案&#xff0c;并重点解释核心算子参数。 一、多类型…

安卓vscodeAI开发实例

前言 前些天发现了一个巨牛的人工智能免费学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 目录 一、安卓开发基础与工具链革新 1.1 Android Studio的局限性分析 1.2 VSCode在移动开发中的崛起 1.3 跨平台开发工具链对比…

③通用搜索---解析FastAdmin中的表格列表的功能

深度解析FastAdmin中的表格列表的功能-CSDN博客文章浏览阅读25次。本文将FastAdmin框架的CRUD功能配置要点进行了系统梳理。官方文档与开发经验相结合&#xff0c;详细介绍了菜单显示、TAB过滤、通用搜索、工具栏按钮、动态统计、快速搜索等17项功能的配置方法。包括字段渲染&a…

DeepSeek 助力 Vue3 开发:打造丝滑的日历(Calendar),日历_项目里程碑示例(CalendarView01_22)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

Python爬虫实战:获取Diesel电商数据并分析

1. 引言 在当今数字化时代,电商平台积累了海量的产品和用户数据。通过对这些数据的挖掘和分析,企业可以深入了解市场动态、消费者需求和竞争态势,从而制定更有效的营销策略和产品规划。Diesel 作为知名的时尚品牌,其在电商平台上的表现备受关注。本研究旨在通过 Python 爬…

Spring RestTemplate + MultiValueMap vs OkHttp 多值参数的处理

&#x1f4cc; Spring RestTemplate vs OkHttp&#xff1a;多值参数处理 一、MultiValueMap 与 FormBody 的差异 特性RestTemplate MultiValueMapOkHttp FormBody多值参数支持✅ 原生支持&#xff08;add("key", "value") 自动追加&#xff09;❌ 需显…

GelSight视触觉3D轮廓仪赋能Beomni人形机器人触觉遥测,开启人形机器人触觉应用新场景

在智能制造、航空航天等领域&#xff0c;传统机器人常面临操作精度不足、环境适应力弱等问题。GelSight触觉传感技术与Beomni人形机器人的融合&#xff0c;为这些场景提供了新可能 —— 通过亚微米级触觉感知能力&#xff0c;操作员可远程感知物体表面细节&#xff0c;在复杂环…

python设置word的字体颜色

这个错误是由于python-docx的RGBColor对象没有.rgb属性导致的。正确的属性访问方式是分别获取红、绿(g)、蓝(b)三个分量。以下是修复方案&#xff1a; 错误原因分析 RGBColor对象的结构如下&#xff1a; from docx.shared import RGBColorcolor RGBColor(255, 204, 51) pri…

推荐模型之GBDT-LR

一、概念 GBDT-LR模型由FaceBook&#xff08;现在的Meta&#xff09;团队于2014年在论文《Practial Lessons from Predicting Clicks on Ads at Facebook》中提出&#xff0c;目标是用于预测FaceBook的广告点击量&#xff08;实际上广告和推荐领域很多算法模型都是共用的&#…

Java实现Excel图片URL筛选与大小检测

Java实现Excel图片URL筛选与大小检测 在数据处理场景中&#xff0c;我们常需筛选Excel中的图片URL。本文分享一个完整的Java方案&#xff0c;涵盖从读取图片URL到检测有效性、筛选大小&#xff0c;再到生成新Excel文件的全过程&#xff0c;同时讲解开发与优化过程&#xff0c;…