在高并发系统设计中,缓存是提升性能的关键策略之一。随着业务的发展,单一的缓存方案往往无法同时兼顾性能、可靠性和一致性等多方面需求。
此时,二级缓存架构应运而生,本文将介绍在Spring Boot中实现二级缓存的三种方案。
一、二级缓存概述
1.1 什么是二级缓存
二级缓存是一种多层次的缓存架构,通常由以下两个层次组成:
- 一级缓存(本地缓存):直接在应用服务器内存中,访问速度极快,但容量有限且在分布式环境下无法共享
- 二级缓存(分布式缓存):独立的缓存服务,如Redis或Memcached,可被多个应用实例共享,容量更大
二级缓存的工作流程通常是:先查询本地缓存,若未命中则查询分布式缓存,仍未命中才访问数据库,并将结果回填到各级缓存中。
1.2 为什么需要二级缓存
单一缓存方案存在明显局限性:
- 仅使用本地缓存:无法在分布式环境下保持数据一致性,每个实例都需要从数据库加载数据
- 仅使用分布式缓存:每次访问都需要网络IO,无法发挥本地缓存的性能优势
二级缓存结合了两者优势:
- 利用本地缓存的高性能,大幅减少网络IO
- 通过分布式缓存保证数据一致性
- 减轻数据库压力,提高系统整体吞吐量
- 更好的故障隔离,即使分布式缓存不可用,本地缓存仍可提供部分服务
二、Spring Cache + Redis方案
2.1 基本原理
该方案利用Spring Cache提供的缓存抽象,配合Caffeine(本地缓存)和Redis(分布式缓存)实现二级缓存。
Spring Cache提供了统一的缓存操作接口,可以通过简单的注解实现缓存功能。
2.2 实现步骤
2.2.1 添加依赖
xml
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 缓存支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Redis支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Caffeine本地缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- 序列化支持 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
2.2.2 配置二级缓存管理器
typescript
@Configuration
@EnableCaching
public class CacheConfig {
@Value("${spring.application.name:app}")
private String appName;
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 创建Redis缓存管理器
RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(getRedisCacheConfigurationWithTtl(3600)) // 默认1小时过期
.withCacheConfiguration("userCache", getRedisCacheConfigurationWithTtl(1800)) // 用户缓存30分钟
.withCacheConfiguration("productCache", getRedisCacheConfigurationWithTtl(7200)) // 产品缓存2小时
.build();
// 创建Caffeine缓存管理器
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100) // 初始容量
.maximumSize(1000) // 最大容量
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入后5分钟过期
.recordStats()); // 开启统计
// 创建二级缓存管理器
return new LayeringCacheManager(caffeineCacheManager, redisCacheManager);
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(long seconds) {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(seconds))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues()
.computePrefixWith(cacheName -> appName + ":" + cacheName + ":");
}
// 二级缓存管理器实现
public static class LayeringCacheManager implements CacheManager {
private final CacheManager localCacheManager;
private final CacheManager remoteCacheManager;
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>();
public LayeringCacheManager(CacheManager localCacheManager, CacheManager remoteCacheManager) {
this.localCacheManager = localCacheManager;
this.remoteCacheManager = remoteCacheManager;
}
@Override
public Cache getCache(String name) {
return cacheMap.computeIfAbsent(name, cacheName -> {
Cache localCache = localCacheManager.getCache(cacheName);
Cache remoteCache = remoteCacheManager.getCache(cacheName);
return new LayeringCache(localCache, remoteCache);
});
}
@Override
public Collection<String> getCacheNames() {
Set<String> names = new LinkedHashSet<>();
names.addAll(localCacheManager.getCacheNames());
names.addAll(remoteCacheManager.getCacheNames());
return names;
}
// 二级缓存实现
static class LayeringCache implements Cache {
private final Cache localCache;
private final Cache remoteCache;
public LayeringCache(Cache localCache, Cache remoteCache) {
this.localCache = localCache;
this.remoteCache = remoteCache;
}
@Override
public String getName() {
return localCache.getName();
}
@Override
public Object getNativeCache() {
return this;
}
@Override
public ValueWrapper get(Object key) {
// 先查本地缓存
ValueWrapper wrapper = localCache.get(key);
if (wrapper != null) {
return wrapper;
}
// 本地未命中,查远程缓存
wrapper = remoteCache.get(key);
if (wrapper != null) {
Object value = wrapper.get();
// 回填本地缓存
localCache.put(key, value);
}
return wrapper;
}
@Override
public <T> T get(Object key, Class<T> type) {
// 先查本地缓存
T value = localCache.get(key, type);
if (value != null) {
return value;
}
// 本地未命中,查远程缓存
value = remoteCache.get(key, type);
if (value != null) {
// 回填本地缓存
localCache.put(key, value);
}
return value;
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
// 先查本地缓存
try {
T value = localCache.get(key, () -> {
// 本地未命中,查远程缓存
try {
return remoteCache.get(key, valueLoader);
} catch (Exception e) {
// 远程缓存未命中或异常,执行valueLoader加载数据
T newValue = valueLoader.call();
if (newValue != null) {
remoteCache.put(key, newValue); // 填充远程缓存
}
return newValue;
}
});
return value;
} catch (Exception e) {
// 本地缓存异常,尝试直接读远程缓存
try {
return remoteCache.get(key, valueLoader);
} catch (Exception ex) {
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new IllegalStateException(ex);
}
}
}
@Override
public void put(Object key, Object value) {
remoteCache.put(key, value); // 先放入远程缓存
localCache.put(key, value); // 再放入本地缓存
}
@Override
public void evict(Object key) {
remoteCache.evict(key); // 先清远程缓存
localCache.evict(key); // 再清本地缓存
}
@Override
public void clear() {
remoteCache.clear(); // 先清远程缓存
localCache.clear(); // 再清本地缓存
}
}
}
}
2.2.3 使用缓存注解
kotlin
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
@Cacheable(cacheNames = "userCache", key = "#id")
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
@Override
@CachePut(cacheNames = "userCache", key = "#user.id")
public User saveUser(User user) {
return userRepository.save(user);
}
@Override
@CacheEvict(cacheNames = "userCache", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
2.2.4 缓存同步问题
在分布式环境下,需要保证缓存一致性。我们可以通过Redis的发布订阅机制实现:
typescript
@Configuration
public class CacheEvictionConfig {
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
@Bean
public RedisCacheMessageListener redisCacheMessageListener(RedisMessageListenerContainer listenerContainer,
CacheManager cacheManager) {
return new RedisCacheMessageListener(listenerContainer, cacheManager);
}
// 缓存消息监听器
public static class RedisCacheMessageListener {
private static final String CACHE_CHANGE_TOPIC = "cache:changes";
private final CacheManager cacheManager;
public RedisCacheMessageListener(RedisMessageListenerContainer listenerContainer, CacheManager cacheManager) {
this.cacheManager = cacheManager;
listenerContainer.addMessageListener((message, pattern) -> {
String body = new String(message.getBody());
CacheChangeMessage cacheMessage = JSON.parseObject(body, CacheChangeMessage.class);
// 清除本地缓存
Cache cache = cacheManager.getCache(cacheMessage.getCacheName());
if (cache != null) {
if (cacheMessage.getKey() != null) {
cache.evict(cacheMessage.getKey());
} else {
cache.clear();
}
}
}, new ChannelTopic(CACHE_CHANGE_TOPIC));
}
}
@Bean
public CacheChangePublisher cacheChangePublisher(RedisTemplate<String, String> redisTemplate) {
return new CacheChangePublisher(redisTemplate);
}
// 缓存变更消息发布器
public static class CacheChangePublisher {
private static final String CACHE_CHANGE_TOPIC = "cache:changes";
private final RedisTemplate<String, String> redisTemplate;
public CacheChangePublisher(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void publishCacheEvict(String cacheName, Object key) {
CacheChangeMessage message = new CacheChangeMessage(cacheName, key);
redisTemplate.convertAndSend(CACHE_CHANGE_TOPIC, JSON.toJSONString(message));
}
public void publishCacheClear(String cacheName) {
CacheChangeMessage message = new CacheChangeMessage(cacheName, null);
redisTemplate.convertAndSend(CACHE_CHANGE_TOPIC, JSON.toJSONString(message));
}
}
// 缓存变更消息
@Data
@AllArgsConstructor
public static class CacheChangeMessage {
private String cacheName;
private Object key;
}
}
2.3 优缺点分析
优点:
- 集成Spring Cache,使用简单,只需通过注解即可实现缓存功能
- 支持多种缓存实现的无缝切换
- 二级缓存逻辑集中管理,便于维护
- 支持缓存失效时间、容量等细粒度控制
缺点:
- 需要自行实现二级缓存管理器,代码相对复杂
- 缓存同步需要额外实现,有一定复杂度
- 自定义缓存加载策略不够灵活
- 对于复杂查询场景支持有限
2.4 适用场景
- 需要快速集成缓存功能的项目
- 使用Spring框架且熟悉Spring Cache机制的团队
- 读多写少的业务场景
- 对缓存一致性要求不是特别高的场景
三、自定义二级缓存框架
3.1 基本原理
该方案通过自定义缓存框架,精确控制缓存的读写流程、失效策略和同步机制,实现更加贴合业务需求的二级缓存。
这种方式虽然实现复杂度高,但提供了最大的灵活性和控制力。
3.2 实现步骤
3.2.1 定义缓存接口
csharp
public interface Cache<K, V> {
V get(K key);
void put(K key, V value);
void remove(K key);
void clear();
long size();
boolean containsKey(K key);
}
public interface CacheLoader<K, V> {
V load(K key);
}
3.2.2 实现本地缓存
typescript
public class LocalCache<K, V> implements Cache<K, V> {
private final com.github.benmanes.caffeine.cache.Cache<K, V> cache;
public LocalCache(long maximumSize, long expireAfterWriteSeconds) {
this.cache = Caffeine.newBuilder()
.maximumSize(maximumSize)
.expireAfterWrite(expireAfterWriteSeconds, TimeUnit.SECONDS)
.recordStats()
.build();
}
@Override
public V get(K key) {
return cache.getIfPresent(key);
}
@Override
public void put(K key, V value) {
if (value != null) {
cache.put(key, value);
}
}
@Override
public void remove(K key) {
cache.invalidate(key);
}
@Override
public void clear() {
cache.invalidateAll();
}
@Override
public long size() {
return cache.estimatedSize();
}
@Override
public boolean containsKey(K key) {
return cache.getIfPresent(key) != null;
}
public CacheStats stats() {
return cache.stats();
}
}
3.2.3 实现Redis分布式缓存
typescript
public class RedisCache<K, V> implements Cache<K, V> {
private final RedisTemplate<String, Object> redisTemplate;
private final String cachePrefix;
private final long expireSeconds;
private final Class<V> valueType;
public RedisCache(RedisTemplate<String, Object> redisTemplate,
String cachePrefix,
long expireSeconds,
Class<V> valueType) {
this.redisTemplate = redisTemplate;
this.cachePrefix = cachePrefix;
this.expireSeconds = expireSeconds;
this.valueType = valueType;
}
private String getCacheKey(K key) {
return cachePrefix + ":" + key.toString();
}
@Override
public V get(K key) {
String cacheKey = getCacheKey(key);
return (V) redisTemplate.opsForValue().get(cacheKey);
}
@Override
public void put(K key, V value) {
if (value != null) {
String cacheKey = getCacheKey(key);
redisTemplate.opsForValue().set(cacheKey, value, expireSeconds, TimeUnit.SECONDS);
}
}
@Override
public void remove(K key) {
String cacheKey = getCacheKey(key);
redisTemplate.delete(cacheKey);
}
@Override
public void clear() {
Set<String> keys = redisTemplate.keys(cachePrefix + ":*");
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
@Override
public long size() {
Set<String> keys = redisTemplate.keys(cachePrefix + ":*");
return keys != null ? keys.size() : 0;
}
@Override
public boolean containsKey(K key) {
String cacheKey = getCacheKey(key);
return Boolean.TRUE.equals(redisTemplate.hasKey(cacheKey));
}
}
3.2.4 实现二级缓存
java
public class TwoLevelCache<K, V> implements Cache<K, V> {
private final Cache<K, V> localCache;
private final Cache<K, V> remoteCache;
private final CacheLoader<K, V> cacheLoader;
private final String cacheName;
private final CacheEventPublisher eventPublisher;
public TwoLevelCache(Cache<K, V> localCache,
Cache<K, V> remoteCache,
CacheLoader<K, V> cacheLoader,
String cacheName,
CacheEventPublisher eventPublisher) {
this.localCache = localCache;
this.remoteCache = remoteCache;
this.cacheLoader = cacheLoader;
this.cacheName = cacheName;
this.eventPublisher = eventPublisher;
}
@Override
public V get(K key) {
// 先查本地缓存
V value = localCache.get(key);
if (value != null) {
return value;
}
// 本地未命中,查远程缓存
value = remoteCache.get(key);
if (value != null) {
// 回填本地缓存
localCache.put(key, value);
return value;
}
// 远程也未命中,加载数据
if (cacheLoader != null) {
value = cacheLoader.load(key);
if (value != null) {
// 填充缓存
put(key, value);
}
}
return value;
}
@Override
public void put(K key, V value) {
if (value != null) {
// 先放入远程缓存,再放入本地缓存
remoteCache.put(key, value);
localCache.put(key, value);
}
}
@Override
public void remove(K key) {
// 先清远程缓存,再清本地缓存
remoteCache.remove(key);
localCache.remove(key);
// 发布缓存失效事件
if (eventPublisher != null) {
eventPublisher.publishCacheEvictEvent(cacheName, key);
}
}
@Override
public void clear() {
// 先清远程缓存,再清本地缓存
remoteCache.clear();
localCache.clear();
// 发布缓存清空事件
if (eventPublisher != null) {
eventPublisher.publishCacheClearEvent(cacheName);
}
}
@Override
public long size() {
return remoteCache.size();
}
@Override
public boolean containsKey(K key) {
return localCache.containsKey(key) || remoteCache.containsKey(key);
}
}
3.2.5 缓存事件发布和订阅
typescript
@Component
public class CacheEventPublisher {
private final RedisTemplate<String, String> redisTemplate;
private static final String CACHE_EVICT_TOPIC = "cache:evict";
private static final String CACHE_CLEAR_TOPIC = "cache:clear";
public CacheEventPublisher(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void publishCacheEvictEvent(String cacheName, Object key) {
Map<String, Object> message = new HashMap<>();
message.put("cacheName", cacheName);
message.put("key", key);
redisTemplate.convertAndSend(CACHE_EVICT_TOPIC, JSON.toJSONString(message));
}
public void publishCacheClearEvent(String cacheName) {
Map<String, Object> message = new HashMap<>();
message.put("cacheName", cacheName);
redisTemplate.convertAndSend(CACHE_CLEAR_TOPIC, JSON.toJSONString(message));
}
}
@Component
public class CacheEventListener {
private final Map<String, TwoLevelCache<?, ?>> cacheMap;
public CacheEventListener(RedisMessageListenerContainer listenerContainer,
Map<String, TwoLevelCache<?, ?>> cacheMap) {
this.cacheMap = cacheMap;
// 监听缓存失效事件
MessageListener evictListener = (message, pattern) -> {
String body = new String(message.getBody());
Map<String, Object> map = JSON.parseObject(body, Map.class);
String cacheName = (String) map.get("cacheName");
Object key = map.get("key");
TwoLevelCache<Object, Object> cache = (TwoLevelCache<Object, Object>) cacheMap.get(cacheName);
if (cache != null) {
// 只清除本地缓存,远程缓存已经由发布者清除
((LocalCache<Object, Object>)cache.getLocalCache()).remove(key);
}
};
// 监听缓存清空事件
MessageListener clearListener = (message, pattern) -> {
String body = new String(message.getBody());
Map<String, Object> map = JSON.parseObject(body, Map.class);
String cacheName = (String) map.get("cacheName");
TwoLevelCache<Object, Object> cache = (TwoLevelCache<Object, Object>) cacheMap.get(cacheName);
if (cache != null) {
// 只清除本地缓存,远程缓存已经由发布者清除
((LocalCache<Object, Object>)cache.getLocalCache()).clear();
}
};
listenerContainer.addMessageListener(evictListener, new ChannelTopic("cache:evict"));
listenerContainer.addMessageListener(clearListener, new ChannelTopic("cache:clear"));
}
}
3.2.6 缓存管理器
swift
@Component
public class TwoLevelCacheManager {
private final RedisTemplate<String, Object> redisTemplate;
private final CacheEventPublisher eventPublisher;
private final Map<String, TwoLevelCache<?, ?>> cacheMap = new ConcurrentHashMap<>();
public TwoLevelCacheManager(RedisTemplate<String, Object> redisTemplate,
CacheEventPublisher eventPublisher) {
this.redisTemplate = redisTemplate;
this.eventPublisher = eventPublisher;
}
public <K, V> TwoLevelCache<K, V> getCache(String cacheName,
Class<V> valueType,
CacheLoader<K, V> cacheLoader) {
return getCache(cacheName, valueType, cacheLoader, 1000, 300, 3600);
}
@SuppressWarnings("unchecked")
public <K, V> TwoLevelCache<K, V> getCache(String cacheName,
Class<V> valueType,
CacheLoader<K, V> cacheLoader,
long localMaxSize,
long localExpireSeconds,
long remoteExpireSeconds) {
return (TwoLevelCache<K, V>) cacheMap.computeIfAbsent(cacheName, name -> {
LocalCache<K, V> localCache = new LocalCache<>(localMaxSize, localExpireSeconds);
RedisCache<K, V> remoteCache = new RedisCache<>(redisTemplate, name, remoteExpireSeconds, valueType);
return new TwoLevelCache<>(localCache, remoteCache, cacheLoader, name, eventPublisher);
});
}
public Map<String, TwoLevelCache<?, ?>> getCacheMap() {
return Collections.unmodifiableMap(cacheMap);
}
}
3.2.7 使用示例
kotlin
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private TwoLevelCacheManager cacheManager;
private TwoLevelCache<Long, User> userCache;
@PostConstruct
public void init() {
userCache = cacheManager.getCache("user", User.class, this::loadUser, 1000, 300, 1800);
}
private User loadUser(Long id) {
return userRepository.findById(id).orElse(null);
}
@Override
public User getUserById(Long id) {
return userCache.get(id);
}
@Override
public User saveUser(User user) {
User savedUser = userRepository.save(user);
userCache.put(user.getId(), savedUser);
return savedUser;
}
@Override
public void deleteUser(Long id) {
userRepository.deleteById(id);
userCache.remove(id);
}
}
3.3 优缺点分析
优点:
- 完全自定义,可以根据业务需求灵活定制
- 精确控制缓存的加载、更新和失效逻辑
- 可以针对不同业务场景设计不同的缓存策略
- 缓存监控和统计更加全面
缺点:
- 开发工作量大,需要实现所有缓存逻辑
- 代码复杂度高,需要考虑多种边界情况
- 不能直接利用Spring等框架提供的缓存抽象
- 维护成本较高
3.4 适用场景
- 对缓存性能和行为有精确控制需求的项目
- 缓存策略复杂,标准框架难以满足的场景
- 大型项目,有专人负责缓存框架开发和维护
- 特殊业务需求,如精确的过期策略、按条件批量失效等
四、JetCache框架方案
4.1 基本原理
JetCache是阿里开源的一款Java缓存抽象框架,原生支持二级缓存,并提供丰富的缓存功能,如缓存自动刷新、异步加载、分布式锁等。它在API设计上类似Spring Cache,但功能更加强大和灵活。
4.2 实现步骤
4.2.1 添加依赖
xml
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JetCache核心 -->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.7.1</version>
</dependency>
</dependencies>
4.2.2 配置JetCache
yaml
# application.yml
jetcache:
statIntervalMinutes: 15
areaInCacheName: false
hidePackages: com.example
local:
default:
type: caffeine
limit: 1000
keyConvertor: fastjson
expireAfterWriteInMillis: 300000 # 5分钟
remote:
default:
type: redis
keyConvertor: fastjson
valueEncoder: java
valueDecoder: java
poolConfig:
minIdle: 5
maxIdle: 20
maxTotal: 50
host: ${redis.host}
port: ${redis.port}
expireAfterWriteInMillis: 1800000 # 30分钟
在启动类上启用JetCache:
less
@SpringBootApplication
@EnableMethodCache(basePackages = "com.example")
@EnableCreateCacheAnnotation
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.2.3 使用注解方式
kotlin
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
@Cached(name = "user:", key = "#id", cacheType = CacheType.BOTH, expire = 1800)
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
@Override
@CacheUpdate(name = "user:", key = "#user.id", value = "#user")
public User saveUser(User user) {
return userRepository.save(user);
}
@Override
@CacheInvalidate(name = "user:", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
4.2.4 使用API方式
kotlin
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductRepository productRepository;
@CreateCache(name = "product:", cacheType = CacheType.BOTH, expire = 3600, localExpire = 600)
private Cache<Long, Product> productCache;
@Override
public Product getProductById(Long id) {
// 自动加载功能,若缓存未命中,会执行lambda中的逻辑并将结果缓存
return productCache.computeIfAbsent(id, this::loadProduct);
}
private Product loadProduct(Long id) {
return productRepository.findById(id).orElse(null);
}
@Override
public Product saveProduct(Product product) {
Product savedProduct = productRepository.save(product);
productCache.put(product.getId(), savedProduct);
return savedProduct;
}
@Override
public void deleteProduct(Long id) {
productRepository.deleteById(id);
productCache.remove(id);
}
// 批量操作
@Override
public List<Product> getProductsByIds(List<Long> ids) {
Map<Long, Product> productMap = productCache.getAll(ids);
List<Long> missedIds = ids.stream()
.filter(id -> !productMap.containsKey(id))
.collect(Collectors.toList());
if (!missedIds.isEmpty()) {
List<Product> missedProducts = productRepository.findAllById(missedIds);
Map<Long, Product> missedProductMap = missedProducts.stream()
.collect(Collectors.toMap(Product::getId, p -> p));
// 更新缓存
productCache.putAll(missedProductMap);
// 合并结果
productMap.putAll(missedProductMap);
}
return ids.stream()
.map(productMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
4.2.5 高级特性:自动刷新和异步加载
typescript
@Service
public class StockServiceImpl implements StockService {
@Autowired
private StockRepository stockRepository;
// 自动刷新缓存,适合库存等频繁变化的数据
@CreateCache(name = "stock:",
cacheType = CacheType.BOTH,
expire = 60, // 1分钟后过期
localExpire = 10, // 本地缓存10秒过期
refreshPolicy = RefreshPolicy.BACKGROUND, // 后台刷新
penetrationProtect = true) // 防止缓存穿透
private Cache<Long, Stock> stockCache;
@Override
public Stock getStockById(Long productId) {
return stockCache.computeIfAbsent(productId, this::loadStock);
}
private Stock loadStock(Long productId) {
return stockRepository.findByProductId(productId).orElse(new Stock(productId, 0));
}
@Override
public void updateStock(Long productId, int newQuantity) {
stockRepository.updateQuantity(productId, newQuantity);
stockCache.remove(productId); // 直接失效缓存,后台自动刷新会加载新值
}
}
4.2.6 缓存统计与监控
less
@RestController
@RequestMapping("/cache")
public class CacheStatsController {
@Autowired
private CacheManager cacheManager;
@GetMapping("/stats")
public Map<String, CacheStats> getCacheStats() {
Collection<Cache> caches = cacheManager.getCache(null);
Map<String, CacheStats> statsMap = new HashMap<>();
for (Cache cache : caches) {
statsMap.put(cache.config().getName(), cache.getStatistics());
}
return statsMap;
}
}
4.3 优缺点分析
优点:
- 原生支持二级缓存,使用简单
- 提供注解和API两种使用方式,灵活性强
- 内置多种高级特性,如自动刷新、异步加载、分布式锁等
- 完善的缓存统计和监控支持
- 社区活跃,文档完善
缺点:
- 增加项目依赖,引入第三方框架
- 配置相对复杂
- 学习成本相对较高
4.4 适用场景
- 需要开箱即用的二级缓存解决方案
- 对缓存有丰富需求的项目,如自动刷新、异步加载等
- 微服务架构,需要统一的缓存抽象
五、总结
选择合适的二级缓存方案需要考虑项目规模、团队技术栈、性能需求、功能需求等多方面因素。
无论选择哪种方案,合理的缓存策略、完善的监控体系和优秀的运维实践都是构建高效缓存系统的关键。
在实际应用中,缓存并非越多越好,应当根据业务特点和系统架构,在性能、复杂度和一致性之间找到平衡点。