导言
主要通过AOP的方式,在redis查询方法上加上自定义注解,实现先查询caffineCache一级缓存,然后再查redis,同时将结果缓存到一级缓存中,支持redis的集合批量查询以及单key查询。
一、导入pom
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.8.6</version>
</dependency>
二、配置caffineCache
1.首先创建CaffineCache的配置属性类
CaffineCacheProperties.java
@Data
public class CaffineCacheProperties {
/**
* cache队列最大大小
*/
private int maxSize = 1000;
/**
* 过期时间,单位s
*/
public int timeoutSecond = 60;
/**
* cache名字
*/
public String cacheName = "default";
}
2.在application.yml或application.properties中增加caffineCache的基本配置
caffine.cache.adx.maxsize=1000
caffine.cache.adx.timeoutsecond=60
caffine.cache.adx.cachename=adx
3.将caffineCache注入到CacheManager
CaffineCacheConfig.java
@Configuration
@EnableCaching
public class CaffineCacheConfig {
@Bean("adxCachePeroperty")
@ConfigurationProperties("caffine.cache.adx")
@DisconfDynamicBean(beanClass = CaffineCacheProperties.class, autoBinding = true, prefix = "caffine.cache.adx")
public CaffineCacheProperties createAdxCacheProperty(){
return new CaffineCacheProperties();
}
/**
* 创建基于Caffeine的Cache Manager
* @return
*/
@Bean
@Primary
public CacheManager caffeineCacheManager(List<CaffineCacheProperties> caffineCacheProperties) {
SimpleCacheManager cacheManager = new SimpleCacheManager();
ArrayList<CaffeineCache> caches = new ArrayList<CaffeineCache>();
for (CaffineCacheProperties caffineCacheProperty:caffineCacheProperties) {
caches.add(new CaffeineCache(caffineCacheProperty.getCacheName(),
Caffeine.newBuilder().recordStats()
.expireAfterWrite(caffineCacheProperty.getTimeoutSecond(), TimeUnit.SECONDS)
.maximumSize(caffineCacheProperty.getMaxSize())
.build())
);
}
cacheManager.setCaches(caches);
return cacheManager;
}
}
三、实现AOP,拦截redis
1.创建自定义注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface EnableCaffeineCache {
String value();
}
2.创建切面
@Aspect
@Component
@Slf4j
@Lazy
public class CaffeineCacheAspect {
// 注入CacheManager
@Autowired
private CacheManager caffineCacheManager;
@Pointcut("@annotation(com.example.aop.EnableCaffeineCache)")
public void caffeineCachePointCut(){}
/**
** 使用around方法对入参及返回进行拦截
*/
@Around("caffeineCachePointCut() && @annotation(caffeinCacheAnnotation)")
public Object injectCaffeinCache(ProceedingJoinPoint proceedingJoinPoint,EnableCaffeineCache caffeinCacheAnnotation) throws Throwable {
String cacheName = caffeinCacheAnnotation.value();
Object[] args = proceedingJoinPoint.getArgs();
Object param = null;
if(args.length==1){
param = args[0];
}else{
// 入参不合法,不开启缓存,直接执行原来的方法
return proceedingJoinPoint.proceed(args);
}
try{
Cache cache = caffineCacheManager.getCache(cacheName);
// 如果是集合,批量查询redis
if(param instanceof Collection){
// 注意 key value 的有序性
Collection collection = (Collection) param;
Map<String,String> resultMap = new LinkedHashMap<>();
List<String> rediskeys = Lists.newArrayList();
// 遍历集合,先去内存缓存查询,没有的 存到redis Map
for(Object key:collection){
String value = cache.get(key, String.class);
resultMap.put((String) key,value);
if (value == null) {
rediskeys.add((String)key);
}
}
// 去redis里面查询,执行原来的方法
List<String> redisValues = (List<String>) proceedingJoinPoint.proceed(new Collection[]{rediskeys});
// 将redis结果缓存到内存缓存中
if(!CollectionUtils.isEmpty(redisValues)){
for(int i=0;i<rediskeys.size();i++){
String value = redisValues.get(i);
resultMap.put(rediskeys.get(i),value);
if(!StringUtils.isEmpty(value)){
cache.put(rediskeys.get(i),value);
}
}
}
// 最后返回 result
Collection<String> values = resultMap.values();
return new ArrayList<>(values);
}else if (param instanceof String){
// 如果是字符串
String s = cache.get(param, String.class);
if(s!=null){
return s;
}else{
String cacheValue= (String) proceedingJoinPoint.proceed(args);
if(cacheValue!=null){
cache.put(param,cacheValue);
}
return cacheValue;
}
}
}catch (Exception e){
log.error("get cache error:{}",e);
}finally {
return proceedingJoinPoint.proceed(args);
}
}
}
3.使用:只需在redis查询方法上加上注解,value为cacheName即可
@EnableCaffeineCache("adx")
private List<String> mGetRedis(Collection<String> redisKeySet) {
List<String> valueList = defaultRedisTemplate.opsForValue().multiGet(redisKeySet);
return valueList;
}
@EnableCaffeineCache("adx")
private String get(String key) {
return (String)defaultRedisTemplate.opsForValue().get(key);
}
四、caffineCache的状态debug接口
@Autowired
private CacheManager caffeine;
@GetMapping("/caffineCache/status")
public ResponseWrapper<Map<String,Object>> caffineCacheStatus(@RequestParam String cacheName) {
CaffeineCache caffeineCache = (CaffeineCache) caffeine.getCache(cacheName);
CacheStats stats = CacheStats.empty();
if (caffeineCache != null) {
stats = caffeineCache.getNativeCache().stats();
}
Map<String, Object> map = new HashMap<>(16);
map.put("请求次数", stats.requestCount());
map.put("命中次数", stats.hitCount());
map.put("命中率", stats.hitCount()*1.0/stats.requestCount());
map.put("加载成功次数", stats.loadSuccessCount());
map.put("加载失败次数", stats.loadFailureCount());
map.put("加载失败占比", stats.loadFailureRate());
map.put("加载总耗时", stats.totalLoadTime());
map.put("回收总次数", stats.evictionCount());
map.put("回收总权重", stats.evictionWeight());
return ResponseWrapper.SUCCESS(map);
}
@GetMapping("/caffineCache/cacheList")
public ResponseWrapper<Collection<String>> caffineCacheList() {
return ResponseWrapper.SUCCESS(caffeine.getCacheNames());
}