SpringBoot整合CaffineCache实现redis的一级缓存,热key问题的一种简单解决

导言

主要通过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());
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿S先森丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值