SpringBoot中二級緩存實(shí)現(xiàn)方案總結(jié)
在高并發(fā)系統(tǒng)設(shè)計(jì)中,緩存是提升性能的關(guān)鍵策略之一。隨著業(yè)務(wù)的發(fā)展,單一的緩存方案往往無法同時(shí)兼顧性能、可靠性和一致性等多方面需求。
此時(shí),二級緩存架構(gòu)應(yīng)運(yùn)而生,本文將介紹在Spring Boot中實(shí)現(xiàn)二級緩存的三種方案。
一、二級緩存概述
1.1 什么是二級緩存
二級緩存是一種多層次的緩存架構(gòu),通常由以下兩個(gè)層次組成:
- 一級緩存(本地緩存):直接在應(yīng)用服務(wù)器內(nèi)存中,訪問速度極快,但容量有限且在分布式環(huán)境下無法共享
- 二級緩存(分布式緩存):獨(dú)立的緩存服務(wù),如Redis或Memcached,可被多個(gè)應(yīng)用實(shí)例共享,容量更大
二級緩存的工作流程通常是:先查詢本地緩存,若未命中則查詢分布式緩存,仍未命中才訪問數(shù)據(jù)庫,并將結(jié)果回填到各級緩存中。
1.2 為什么需要二級緩存
單一緩存方案存在明顯局限性:
- 僅使用本地緩存:無法在分布式環(huán)境下保持?jǐn)?shù)據(jù)一致性,每個(gè)實(shí)例都需要從數(shù)據(jù)庫加載數(shù)據(jù)
- 僅使用分布式緩存:每次訪問都需要網(wǎng)絡(luò)IO,無法發(fā)揮本地緩存的性能優(yōu)勢
二級緩存結(jié)合了兩者優(yōu)勢:
- 利用本地緩存的高性能,大幅減少網(wǎng)絡(luò)IO
- 通過分布式緩存保證數(shù)據(jù)一致性
- 減輕數(shù)據(jù)庫壓力,提高系統(tǒng)整體吞吐量
- 更好的故障隔離,即使分布式緩存不可用,本地緩存仍可提供部分服務(wù)
二、Spring Cache + Redis方案
2.1 基本原理
該方案利用Spring Cache提供的緩存抽象,配合Caffeine(本地緩存)和Redis(分布式緩存)實(shí)現(xiàn)二級緩存。
Spring Cache提供了統(tǒng)一的緩存操作接口,可以通過簡單的注解實(shí)現(xiàn)緩存功能。
2.2 實(shí)現(xiàn)步驟
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;
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 創(chuàng)建Redis緩存管理器
RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(getRedisCacheConfigurationWithTtl(3600)) // 默認(rèn)1小時(shí)過期
.withCacheConfiguration("userCache", getRedisCacheConfigurationWithTtl(1800)) // 用戶緩存30分鐘
.withCacheConfiguration("productCache", getRedisCacheConfigurationWithTtl(7200)) // 產(chǎn)品緩存2小時(shí)
.build();
// 創(chuàng)建Caffeine緩存管理器
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100) // 初始容量
.maximumSize(1000) // 最大容量
.expireAfterWrite(5, TimeUnit.MINUTES) // 寫入后5分鐘過期
.recordStats()); // 開啟統(tǒng)計(jì)
// 創(chuàng)建二級緩存管理器
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 + ":");
}
// 二級緩存管理器實(shí)現(xiàn)
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;
}
// 二級緩存實(shí)現(xiàn)
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;
}
// 本地未命中,查遠(yuǎn)程緩存
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;
}
// 本地未命中,查遠(yuǎn)程緩存
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, () -> {
// 本地未命中,查遠(yuǎn)程緩存
try {
return remoteCache.get(key, valueLoader);
} catch (Exception e) {
// 遠(yuǎn)程緩存未命中或異常,執(zhí)行valueLoader加載數(shù)據(jù)
T newValue = valueLoader.call();
if (newValue != null) {
remoteCache.put(key, newValue); // 填充遠(yuǎn)程緩存
}
return newValue;
}
});
return value;
} catch (Exception e) {
// 本地緩存異常,嘗試直接讀遠(yuǎn)程緩存
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); // 先放入遠(yuǎn)程緩存
localCache.put(key, value); // 再放入本地緩存
}
@Override
public void evict(Object key) {
remoteCache.evict(key); // 先清遠(yuǎn)程緩存
localCache.evict(key); // 再清本地緩存
}
@Override
public void clear() {
remoteCache.clear(); // 先清遠(yuǎn)程緩存
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 緩存同步問題
在分布式環(huán)境下,需要保證緩存一致性。我們可以通過Redis的發(fā)布訂閱機(jī)制實(shí)現(xiàn):
@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);
}
// 緩存消息監(jiān)聽器
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);
}
// 緩存變更消息發(fā)布器
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 優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
1. 集成Spring Cache,使用簡單,只需通過注解即可實(shí)現(xiàn)緩存功能
2. 支持多種緩存實(shí)現(xiàn)的無縫切換
3. 二級緩存邏輯集中管理,便于維護(hù)
4. 支持緩存失效時(shí)間、容量等細(xì)粒度控制
缺點(diǎn):
1. 需要自行實(shí)現(xiàn)二級緩存管理器,代碼相對復(fù)雜
2. 緩存同步需要額外實(shí)現(xiàn),有一定復(fù)雜度
3. 自定義緩存加載策略不夠靈活
4. 對于復(fù)雜查詢場景支持有限
2.4 適用場景
- 需要快速集成緩存功能的項(xiàng)目
- 使用Spring框架且熟悉Spring Cache機(jī)制的團(tuán)隊(duì)
- 讀多寫少的業(yè)務(wù)場景
- 對緩存一致性要求不是特別高的場景
三、自定義二級緩存框架
3.1 基本原理
該方案通過自定義緩存框架,精確控制緩存的讀寫流程、失效策略和同步機(jī)制,實(shí)現(xiàn)更加貼合業(yè)務(wù)需求的二級緩存。
這種方式雖然實(shí)現(xiàn)復(fù)雜度高,但提供了最大的靈活性和控制力。
3.2 實(shí)現(xiàn)步驟
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 實(shí)現(xiàn)本地緩存
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 實(shí)現(xiàn)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 實(shí)現(xiàn)二級緩存
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;
}
// 本地未命中,查遠(yuǎn)程緩存
value = remoteCache.get(key);
if (value != null) {
// 回填本地緩存
localCache.put(key, value);
return value;
}
// 遠(yuǎn)程也未命中,加載數(shù)據(jù)
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) {
// 先放入遠(yuǎn)程緩存,再放入本地緩存
remoteCache.put(key, value);
localCache.put(key, value);
}
}
@Override
public void remove(K key) {
// 先清遠(yuǎn)程緩存,再清本地緩存
remoteCache.remove(key);
localCache.remove(key);
// 發(fā)布緩存失效事件
if (eventPublisher != null) {
eventPublisher.publishCacheEvictEvent(cacheName, key);
}
}
@Override
public void clear() {
// 先清遠(yuǎn)程緩存,再清本地緩存
remoteCache.clear();
localCache.clear();
// 發(fā)布緩存清空事件
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 緩存事件發(fā)布和訂閱
@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;
// 監(jiān)聽緩存失效事件
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) {
// 只清除本地緩存,遠(yuǎn)程緩存已經(jīng)由發(fā)布者清除
((LocalCache<Object, Object>)cache.getLocalCache()).remove(key);
}
};
// 監(jiān)聽緩存清空事件
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) {
// 只清除本地緩存,遠(yuǎn)程緩存已經(jīng)由發(fā)布者清除
((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 優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
1. 完全自定義,可以根據(jù)業(yè)務(wù)需求靈活定制
2. 精確控制緩存的加載、更新和失效邏輯
3. 可以針對不同業(yè)務(wù)場景設(shè)計(jì)不同的緩存策略
4. 緩存監(jiān)控和統(tǒng)計(jì)更加全面
缺點(diǎn):
1. 開發(fā)工作量大,需要實(shí)現(xiàn)所有緩存邏輯
2. 代碼復(fù)雜度高,需要考慮多種邊界情況
3. 不能直接利用Spring等框架提供的緩存抽象
4. 維護(hù)成本較高
3.4 適用場景
- 對緩存性能和行為有精確控制需求的項(xiàng)目
- 緩存策略復(fù)雜,標(biāo)準(zhǔn)框架難以滿足的場景
- 大型項(xiàng)目,有專人負(fù)責(zé)緩存框架開發(fā)和維護(hù)
- 特殊業(yè)務(wù)需求,如精確的過期策略、按條件批量失效等
四、JetCache框架方案
4.1 基本原理
JetCache是阿里開源的一款Java緩存抽象框架,原生支持二級緩存,并提供豐富的緩存功能,如緩存自動(dòng)刷新、異步加載、分布式鎖等。
它在API設(shè)計(jì)上類似Spring Cache,但功能更加強(qiáng)大和靈活。
4.2 實(shí)現(xiàn)步驟
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: 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分鐘在啟動(dòng)類上啟用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) {
// 自動(dòng)加載功能,若緩存未命中,會(huì)執(zhí)行l(wèi)ambda中的邏輯并將結(jié)果緩存
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);
// 合并結(jié)果
productMap.putAll(missedProductMap);
}
return ids.stream()
.map(productMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}4.2.5 高級特性:自動(dòng)刷新和異步加載
@Service
public class StockServiceImpl implements StockService {
@Autowired
private StockRepository stockRepository;
// 自動(dòng)刷新緩存,適合庫存等頻繁變化的數(shù)據(jù)
@CreateCache(name = "stock:",
cacheType = CacheType.BOTH,
expire = 60, // 1分鐘后過期
localExpire = 10, // 本地緩存10秒過期
refreshPolicy = RefreshPolicy.BACKGROUND, // 后臺(tái)刷新
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); // 直接失效緩存,后臺(tái)自動(dòng)刷新會(huì)加載新值
}
}4.2.6 緩存統(tǒng)計(jì)與監(jiān)控
@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 優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn):
1. 原生支持二級緩存,使用簡單
2. 提供注解和API兩種使用方式,靈活性強(qiáng)
3. 內(nèi)置多種高級特性,如自動(dòng)刷新、異步加載、分布式鎖等
4. 完善的緩存統(tǒng)計(jì)和監(jiān)控支持
5. 社區(qū)活躍,文檔完善
缺點(diǎn):
1. 增加項(xiàng)目依賴,引入第三方框架
2. 配置相對復(fù)雜
3. 學(xué)習(xí)成本相對較高
4.4 適用場景
- 需要開箱即用的二級緩存解決方案
- 對緩存有豐富需求的項(xiàng)目,如自動(dòng)刷新、異步加載等
- 微服務(wù)架構(gòu),需要統(tǒng)一的緩存抽象
五、總結(jié)
選擇合適的二級緩存方案需要考慮項(xiàng)目規(guī)模、團(tuán)隊(duì)技術(shù)棧、性能需求、功能需求等多方面因素。
無論選擇哪種方案,合理的緩存策略、完善的監(jiān)控體系和優(yōu)秀的運(yùn)維實(shí)踐都是構(gòu)建高效緩存系統(tǒng)的關(guān)鍵。
在實(shí)際應(yīng)用中,緩存并非越多越好,應(yīng)當(dāng)根據(jù)業(yè)務(wù)特點(diǎn)和系統(tǒng)架構(gòu),在性能、復(fù)雜度和一致性之間找到平衡點(diǎn)。
到此這篇關(guān)于SpringBoot中二級緩存實(shí)現(xiàn)方案總結(jié)的文章就介紹到這了,更多相關(guān)SpringBoot二級緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JSP request.setAttribute()詳解及實(shí)例
這篇文章主要介紹了 javascript request.setAttribute()詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02
Java(SpringBoot)項(xiàng)目打包(構(gòu)建)成Docker鏡像的幾種常見方式
在對Spring Boot應(yīng)用程序進(jìn)行Docker化時(shí),為應(yīng)用程序選擇正確的基礎(chǔ)鏡像非常重要,下面這篇文章主要給大家介紹了關(guān)于Java(SpringBoot)項(xiàng)目打包(構(gòu)建)成Docker鏡像的幾種常見方式,需要的朋友可以參考下2023-12-12
Spring注解驅(qū)動(dòng)之BeanPostProcessor后置處理器講解
這篇文章主要介紹了Spring注解驅(qū)動(dòng)之BeanPostProcessor后置處理器講解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
Springboot的自動(dòng)配置是什么及注意事項(xiàng)
SpringBoot的自動(dòng)配置(Auto-configuration)是指框架根據(jù)項(xiàng)目的依賴和應(yīng)用程序的環(huán)境自動(dòng)配置Spring應(yīng)用上下文中的Bean和組件,目的是簡化開發(fā)者的配置工作,本文介紹Springboot的自動(dòng)配置是什么及注意事項(xiàng),感興趣的朋友一起看看吧2025-03-03
JVM內(nèi)存管理之JAVA語言的內(nèi)存管理詳解
下面小編就為大家?guī)硪黄狫VM內(nèi)存管理之JAVA語言的內(nèi)存管理詳解。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08
Spring中@Autowired和@Qualifier注解的3個(gè)知識(shí)點(diǎn)小結(jié)
這篇文章主要介紹了Spring中@Autowired和@Qualifier注解的3個(gè)知識(shí)點(diǎn)小結(jié),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09

