SpringBoot Caffeine+Redisson配置二級緩存實踐
問題說明
在高性能的服務架構設計中,緩存是一個不可或缺的環(huán)節(jié)。在實際的項目中,我們通常會將一些熱點數(shù)據(jù)存儲到Redis或MemCache這類緩存中間件中,只有當緩存的訪問沒有命中時再查詢數(shù)據(jù)庫。在提升訪問速度的同時,也能降低數(shù)據(jù)庫的壓力。
隨著不斷的發(fā)展,這一架構也產生了改進,在一些場景下可能單純使用Redis類的遠程緩存已經(jīng)不夠了,還需要進一步配合本地緩存使用,例如Guava cache或Caffeine,從而再次提升程序的響應速度與服務性能。于是,就產生了使用本地緩存作為一級緩存,再加上遠程緩存作為二級緩存的兩級緩存架構。

準備
集成Redission:SpringBoot 整合Redisson重寫cacheName支持多參數(shù)
<!--caffeine-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>配置文件
增加本地緩存是為了解決高并發(fā)下頻繁查詢Redis的問題,配置了expireAfterWrite最后一次寫入或訪問后經(jīng)過固定時間過期為30秒,例如一次訪問一個頁面一直刷新,在30秒內無論怎么刷新都會走本地緩存,而且就算在29秒重新獲取了緩存,也會重新計算30秒
@EnableCaching開啟緩存功能,也可以加在啟動類上
package com.example.redisson.config;
import com.example.redisson.manager.PlusSpringCacheManager;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
/**
* 緩存配置
*
* @author Lion Li
*/
@Configuration
@EnableCaching
public class CacheConfig {
/**
* caffeine 本地緩存處理器
*/
@Bean
public Cache<Object, Object> caffeine() {
return Caffeine.newBuilder()
// 設置最后一次寫入或訪問后經(jīng)過固定時間過期
.expireAfterWrite(30, TimeUnit.SECONDS)
// 初始的緩存空間大小
.initialCapacity(100)
// 緩存的最大條數(shù)
.maximumSize(1000)
.build();
}
/**
* 自定義緩存管理器 整合spring-cache
*/
@Bean
public CacheManager cacheManager() {
return new PlusSpringCacheManager();
}
}
裝飾器
- put方法是cache使用的也就是Redission ,會自動加上cacheNames
- 而caffeine不使用cacheNames ,get方法的key是哪個就會緩存哪個key
- 所以為了解決key相同,cacheNames 不同的問題增加了getUniqueKey方法
getName( ) = cacheNames
一個CacheNams對應一個Cache對象
package org.dromara.common.redis.manager;
import cn.hutool.core.lang.Console;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.cache.Cache;
import java.util.concurrent.Callable;
/**
* Cache 裝飾器模式(用于擴展 Caffeine 一級緩存)
*
* @author LionLi
*/
public class CaffeineCacheDecorator implements Cache {
private static final com.github.benmanes.caffeine.cache.Cache<Object, Object>
CAFFEINE = SpringUtils.getBean("caffeine");
private final Cache cache;
public CaffeineCacheDecorator(Cache cache) {
this.cache = cache;
}
@Override
public String getName() {
return cache.getName();
}
@Override
public Object getNativeCache() {
return cache.getNativeCache();
}
public String getUniqueKey(Object key) {
return cache.getName() + ":" + key;
}
@Override
public ValueWrapper get(Object key) {
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key));
Console.log("redisson caffeine -> key: " + getUniqueKey(key) + ",value:" + o);
return (ValueWrapper) o;
}
@SuppressWarnings("unchecked")
public <T> T get(Object key, Class<T> type) {
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, type));
Console.log("redisson caffeine -> key: " + getUniqueKey(key) + ",value:" + o);
return (T) o;
}
@Override
public void put(Object key, Object value) {
cache.put(key, value);
}
public ValueWrapper putIfAbsent(Object key, Object value) {
return cache.putIfAbsent(key, value);
}
@Override
public void evict(Object key) {
evictIfPresent(key);
}
public boolean evictIfPresent(Object key) {
boolean b = cache.evictIfPresent(key);
if (b) {
CAFFEINE.invalidate(getUniqueKey(key));
}
return b;
}
@Override
public void clear() {
cache.clear();
}
public boolean invalidate() {
return cache.invalidate();
}
@SuppressWarnings("unchecked")
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
Object o = CAFFEINE.get(getUniqueKey(key), k -> cache.get(key, valueLoader));
Console.log("redisson caffeine -> key: " + getUniqueKey(key) + ",value:" + o);
return (T) o;
}
}
修改自定義PlusSpringCacheManager,注入裝飾器

使用
@Cacheable(cacheNames = "demo:cache#60s#10m#20", key = "#key", condition = "#key != null")
@GetMapping("/test1")
public R<String> test1(String key, String value) {
System.out.println("test1-->調用方法體");
return R.ok("操作成功", value);
}
因為@Cacheable注解上來就會調用get方法,所以可以觸發(fā)本地緩存,@CachePut注解則不行

查看源碼可知,如果一級沒有獲取到,則獲取二級

緩存工具類
package com.example.redisson.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.RMap;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import java.util.Set;
/**
* 緩存操作工具類 {@link }
*
* @author Michelle.Chung
* @date 2022/8/13
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings(value = {"unchecked"})
public class CacheUtils {
private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);
/**
* 獲取緩存組內所有的KEY
*
* @param cacheNames 緩存組名稱
*/
public static Set<Object> keys(String cacheNames) {
RMap<Object, Object> rmap = (RMap<Object, Object>) CACHE_MANAGER.getCache(cacheNames).getNativeCache();
return rmap.keySet();
}
/**
* 獲取緩存值
*
* @param cacheNames 緩存組名稱
* @param key 緩存key
*/
public static <T> T get(String cacheNames, Object key) {
Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key);
return wrapper != null ? (T) wrapper.get() : null;
}
/**
* 保存緩存值
*
* @param cacheNames 緩存組名稱
* @param key 緩存key
* @param value 緩存值
*/
public static void put(String cacheNames, Object key, Object value) {
CACHE_MANAGER.getCache(cacheNames).put(key, value);
}
/**
* 刪除緩存值
*
* @param cacheNames 緩存組名稱
* @param key 緩存key
*/
public static void evict(String cacheNames, Object key) {
CACHE_MANAGER.getCache(cacheNames).evict(key);
}
/**
* 清空緩存值
*
* @param cacheNames 緩存組名稱
*/
public static void clear(String cacheNames) {
CACHE_MANAGER.getCache(cacheNames).clear();
}
}
總結
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
spring mvc 實現(xiàn)獲取后端傳遞的值操作示例
這篇文章主要介紹了spring mvc 實現(xiàn)獲取后端傳遞的值操作,結合實例形式詳細分析了spring mvc使用JSTL 方法獲取后端傳遞的值相關操作技巧2019-11-11
Java并發(fā)編程Callable與Future的應用實例代碼
這篇文章主要介紹了Java并發(fā)編程Callable與Future的應用實例代碼,具有一定借鑒價值,需要的朋友可以參考下2018-01-01
SpringBoot動態(tài)修改yml配置文件的方法詳解
這篇文章主要為大家詳細介紹了SpringBoot動態(tài)修改yml配置文件的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03
springcloud中Feign超時提示Read timed out executing
Feign接口調用分兩層,Ribbon的調用和Hystrix調用,理論上設置Ribbon的時間即可,但是Ribbon的超時時間和Hystrix的超時時間需要結合起來,這篇文章給大家介紹springcloud之Feign超時提示Read timed out executing POST問題及解決方法,感興趣的朋友一起看看吧2024-01-01

