layering-cache扩展——多级缓存有效时间增加随机浮动时间,防止缓存雪崩

一、背景

最近在项目中有使用layering-cache(https://gitee.com/xiaolyuh/layering-cache?_from=gitee_search)进行多级缓存,有开发人员说想在缓存的时候增加一定时间的随机浮动,已到达防止缓存雪崩的目的,由于layering-cache是开源的,想增加此功能也十分简单,在此记录一下。

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。

二、编码实现

本次改造只针对二级缓存,也就是redis缓存层面做代码改造,一级缓存暂时不管,代码实现如下:

  1. 在二级缓存SecondaryCache注解类中增加浮动时间属性,代码如下:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SecondaryCache {
    /**
     * 缓存有效时间
     *
     * @return long
     */
    long expireTime() default 5;

    /**
     * 缓存主动在失效前强制刷新缓存的时间
     * 建议是: preloadTime = expireTime * 0.2
     *
     * @return long
     */
    long preloadTime() default 1;

    /**
     * 时间单位 {@link TimeUnit}
     *
     * @return TimeUnit
     */
    TimeUnit timeUnit() default TimeUnit.HOURS;

    /**
     * 是否强制刷新(直接执行被缓存方法),默认是false
     *
     * @return boolean
     */
    boolean forceRefresh() default false;

    /**
     * 非空值和null值之间的时间倍率,默认是1。isAllowNullValue=true才有效
     * 

* 如配置缓存的有效时间是200秒,倍率这设置成10, * 那么当缓存value为null时,缓存的有效时间将是20秒,非空时为200秒 *

* * @return int */
int magnification() default 1; /** * 是否允许null值 * * @return boolean */ boolean isAllowNullValue() default false; /** * 是否开启内置防雪崩策略 * * @return boolean */ boolean isPreventAvalanche() default false; /** * 缓存有效时间的浮动时间 * * @return long */ long floatTime() default 0; /** * 浮动时间单位 * * @return TimeUnit */ TimeUnit floatTimeUnit() default TimeUnit.SECONDS; }
  1. LayeringAspect切面类中,增加时间浮动策略方法,代码如下:
private static final Logger log = LoggerFactory.getLogger(LayeringAspect.class);

    /**
     * 有效时间单位为天时的浮动小时数
     */
    private static final int DAYS_FLOAT_HOURS = 1;

    /**
     * 有效时间为小时时的浮动分钟数
     */
    private static final int HOURS_FLOAT_MINUTES = 1;

    /**
     * 二级缓存中增加有效时间浮动策略防雪崩
     *
     * @param secondaryCache
     * @return SecondaryCacheSetting
     */
    private SecondaryCacheSetting secondaryCacheHandler(SecondaryCache secondaryCache) {
        SecondaryCacheSetting secondaryCacheSetting = new SecondaryCacheSetting();
        secondaryCacheSetting.setPreloadTime(secondaryCache.preloadTime());
        secondaryCacheSetting.setAllowNullValue(secondaryCache.isAllowNullValue());
        secondaryCacheSetting.setForceRefresh(secondaryCache.forceRefresh());
        secondaryCacheSetting.setMagnification(secondaryCache.magnification());

        TimeUnit timeUnit = secondaryCache.timeUnit(); // 缓存有效时间单位
        long floatTime = secondaryCache.floatTime(); // 缓存浮动时间

        if (floatTime > 0) { // 如果设置了浮动时间
            TimeUnit floatTimeUnit = secondaryCache.floatTimeUnit(); // 浮动时间单位
            long expireTime = secondaryCache.expireTime(); // 有效时间
            if (floatTimeUnit.compareTo(timeUnit) <= 0) { // 浮动时间单位小于等于有效时间单位
                TimeUnit tempTimeUtil = floatTimeUnit;
                if (floatTimeUnit.compareTo(TimeUnit.SECONDS) > 0) { // 如果浮动时间单位大于秒,为了随机均匀一些,转成秒操作
                    tempTimeUtil = TimeUnit.SECONDS;
                    expireTime = TimeUnit.SECONDS.convert(expireTime, timeUnit);
                    floatTime = TimeUnit.SECONDS.convert(floatTime, floatTimeUnit);
                } else {
                    expireTime = floatTimeUnit.convert(expireTime, timeUnit);
                }
                expireTime = ThreadLocalRandom.current().nextLong(expireTime, expireTime + floatTime + 1L);
                secondaryCacheSetting.setExpiration(expireTime);
                secondaryCacheSetting.setTimeUnit(tempTimeUtil);
            } else {
                log.error("float time unit {} more than expire time unit {} !", floatTimeUnit, timeUnit);
                throw new RuntimeException("float time unit more than expire time unit !");
            }
        } else if (secondaryCache.isPreventAvalanche()) { // 启用内置防雪崩策略
            switch (timeUnit) {
                case DAYS: // 有效时间单位为天时,进行DAYS_FLOAT_HOURS个小时(DAYS_FLOAT_HOURS * 3600s)的随机浮动操作
                    long day_expireTimeSeconds = TimeUnit.SECONDS.convert(secondaryCache.expireTime(), timeUnit);
                    day_expireTimeSeconds = ThreadLocalRandom.current().nextLong(day_expireTimeSeconds, day_expireTimeSeconds + DAYS_FLOAT_HOURS * 60 * 60L + 1);

                    secondaryCacheSetting.setExpiration(day_expireTimeSeconds);
                    secondaryCacheSetting.setTimeUnit(TimeUnit.SECONDS);
                    break;
                case HOURS: // 有效时间单位为小时时,进行HOURS_FLOAT_MINUTES分钟(HOURS_FLOAT_MINUTES * 60s)的随机浮动操作
                    long hour_expireTimeSeconds = TimeUnit.SECONDS.convert(secondaryCache.expireTime(), timeUnit);
                    hour_expireTimeSeconds = ThreadLocalRandom.current().nextLong(hour_expireTimeSeconds, hour_expireTimeSeconds + HOURS_FLOAT_MINUTES * 60L + 1);

                    secondaryCacheSetting.setExpiration(hour_expireTimeSeconds);
                    secondaryCacheSetting.setTimeUnit(TimeUnit.SECONDS);
                    break;
                default:
                    break;
            }
        } else { // 如果进行有效时间浮动,则进行默认配置
            secondaryCacheSetting.setExpiration(secondaryCache.expireTime());
            secondaryCacheSetting.setTimeUnit(timeUnit);
        }

        return secondaryCacheSetting;
    }
  1. LayeringAspect切面类中修改二级缓存设置对象的创建方式,代码如下:
  • executeCacheable方法
 SecondaryCacheSetting secondaryCacheSetting = secondaryCacheHandler(secondaryCache);
  • executePut方法
 SecondaryCacheSetting secondaryCacheSetting = secondaryCacheHandler(secondaryCache);
  1. LayeringAspect切面类完整代码可参考如下:
package com.github.xiaolyuh.aspect;

import com.github.xiaolyuh.annotation.*;
import com.github.xiaolyuh.cache.Cache;
import com.github.xiaolyuh.expression.CacheOperationExpressionEvaluator;
import com.github.xiaolyuh.manager.CacheManager;
import com.github.xiaolyuh.redis.serializer.SerializationException;
import com.github.xiaolyuh.setting.FirstCacheSetting;
import com.github.xiaolyuh.setting.LayeringCacheSetting;
import com.github.xiaolyuh.setting.SecondaryCacheSetting;
import com.github.xiaolyuh.support.CacheMode;
import com.github.xiaolyuh.support.KeyGenerator;
import com.github.xiaolyuh.support.SimpleKeyGenerator;
import com.github.xiaolyuh.util.ToStringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
 * 缓存拦截,用于注册方法信息
 *
 * @author yuhao.wang
 */
@Aspect
public class LayeringAspect {
    private static final String CACHE_KEY_ERROR_MESSAGE = "缓存Key %s 不能为NULL";
    private static final String CACHE_NAME_ERROR_MESSAGE = "缓存名称不能为NULL";

    private static final Logger log = LoggerFactory.getLogger(LayeringAspect.class);

    /**
     * 有效时间单位为天时的浮动小时数
     */
    private static final int DAYS_FLOAT_HOURS = 1;

    /**
     * 有效时间为小时时的浮动分钟数
     */
    private static final int HOURS_FLOAT_MINUTES = 1;
    
    /**
     * SpEL表达式计算器
     */
    private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator();

    @Autowired
    private CacheManager cacheManager;

    @Autowired(required = false)
    private KeyGenerator keyGenerator = new SimpleKeyGenerator();

    @Pointcut("@annotation(com.github.xiaolyuh.annotation.Cacheable)")
    public void cacheablePointcut() {
    }

    @Pointcut("@annotation(com.github.xiaolyuh.annotation.CacheEvict)")
    public void cacheEvictPointcut() {
    }

    @Pointcut("@annotation(com.github.xiaolyuh.annotation.CachePut)")
    public void cachePutPointcut() {
    }

    @Pointcut("@annotation(com.github.xiaolyuh.annotation.Caching)")
    public void cachingPointcut() {
    }

    @Around("cacheablePointcut()")
    public Object cacheablePointcut(ProceedingJoinPoint joinPoint) throws Throwable {

        // 获取method
        Method method = this.getSpecificmethod(joinPoint);
        // 获取注解
        Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
        assert cacheable != null;

        try {
            // 执行查询缓存方法
            return executeCacheable(getCacheOperationInvoker(joinPoint), cacheable, method, joinPoint.getArgs(), joinPoint.getTarget());
        } catch (SerializationException e) {
            // 如果是序列化异常需要先删除原有缓存,在执行缓存方法
            String[] cacheNames = cacheable.cacheNames();
            delete(cacheNames, cacheable.key(), method, joinPoint.getArgs(), joinPoint.getTarget());
            return executeCacheable(getCacheOperationInvoker(joinPoint), cacheable, method, joinPoint.getArgs(), joinPoint.getTarget());
        }
    }

    @Around("cacheEvictPointcut()")
    public Object cacheEvictPointcut(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取method
        Method method = this.getSpecificmethod(joinPoint);
        // 获取注解
        CacheEvict cacheEvict = AnnotationUtils.findAnnotation(method, CacheEvict.class);
        assert cacheEvict != null;

        // 执行删除方法,先执行方法的目的是防止回写脏数据到缓存
        Object result = joinPoint.proceed();
        executeEvict(cacheEvict, method, joinPoint.getArgs(), joinPoint.getTarget());
        return result;
    }

    @Around("cachePutPointcut()")
    public Object cachePutPointcut(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取method
        Method method = this.getSpecificmethod(joinPoint);
        // 获取注解
        CachePut cachePut = AnnotationUtils.findAnnotation(method, CachePut.class);
        assert cachePut != null;

        // 指定调用方法获取缓存值
        Object result = joinPoint.proceed();
        executePut(result, cachePut, method, joinPoint.getArgs(), joinPoint.getTarget());
        return result;
    }

    @Around("cachingPointcut()")
    public Object cachingPointcut(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取method
        Method method = this.getSpecificmethod(joinPoint);
        // 获取注解
        Caching caching = AnnotationUtils.findAnnotation(method, Caching.class);
        assert caching != null;
        Cacheable[] cacheables = caching.cacheable();
        CachePut[] puts = caching.put();
        CacheEvict[] evicts = caching.evict();

        Object result = evicts.length > 0 ? joinPoint.proceed() : EmptyObject.getInstance();
        for (CacheEvict cacheEvict : evicts) {
            executeEvict(cacheEvict, method, joinPoint.getArgs(), joinPoint.getTarget());
        }

        result = result instanceof EmptyObject && puts.length > 0 ? joinPoint.proceed() : result;
        for (CachePut cachePut : puts) {
            executePut(result, cachePut, method, joinPoint.getArgs(), joinPoint.getTarget());
        }

        for (Cacheable cacheable : cacheables) {
            Object finalResult = result;
            Callable callable = result instanceof EmptyObject ? getCacheOperationInvoker(joinPoint) : () -> finalResult;
            result = executeCacheable(callable, cacheable, method, joinPoint.getArgs(), joinPoint.getTarget());
        }

        // 执行查询缓存方法
        return isEmptyAnnotation(result, cacheables, puts, evicts) ? joinPoint.proceed() : result;
    }

    /**
     * 是否是空注解
     *
     * @param cacheables Cacheable注解
     * @param puts       CachePut注解
     * @param evicts     CacheEvict注解
     * @return boolean
     */
    private boolean isEmptyAnnotation(Object result, Cacheable[] cacheables, CachePut[] puts, CacheEvict[] evicts) {
        return result instanceof EmptyObject && cacheables.length == 0 && puts.length == 0 && evicts.length == 0;
    }

    /**
     * 执行Cacheable切面
     *
     * @param valueLoader 加载缓存的回调方法
     * @param cacheable   {@link Cacheable}
     * @param method      {@link Method}
     * @param args        注解方法参数
     * @param target      target
     * @return {@link Object}
     */
    private Object executeCacheable(Callable valueLoader, Cacheable cacheable, Method method, Object[] args, Object target) {

        // 解析SpEL表达式获取cacheName和key
        String[] cacheNames = cacheable.cacheNames();
        Assert.notEmpty(cacheable.cacheNames(), CACHE_NAME_ERROR_MESSAGE);
        String cacheName = cacheNames[0];
        Object key = generateKey(cacheable.key(), method, args, target);
        Assert.notNull(key, String.format(CACHE_KEY_ERROR_MESSAGE, cacheable.key()));

        // 从注解中获取缓存配置
        FirstCache firstCache = cacheable.firstCache();
        SecondaryCache secondaryCache = cacheable.secondaryCache();
        FirstCacheSetting firstCacheSetting = new FirstCacheSetting(firstCache.initialCapacity(), firstCache.maximumSize(),
                firstCache.expireTime(), firstCache.timeUnit(), firstCache.expireMode());

        SecondaryCacheSetting secondaryCacheSetting = secondaryCacheHandler(secondaryCache);

        LayeringCacheSetting layeringCacheSetting = new LayeringCacheSetting(firstCacheSetting, secondaryCacheSetting,
                cacheable.depict(), cacheable.cacheMode());

        // 通过cacheName和缓存配置获取Cache
        Cache cache = cacheManager.getCache(cacheName, layeringCacheSetting);

        // 通Cache获取值
        return cache.get(ToStringUtils.toString(key), method.getReturnType(), valueLoader);
    }

    /**
     * 执行 CacheEvict 切面
     *
     * @param cacheEvict {@link CacheEvict}
     * @param method     {@link Method}
     * @param args       注解方法参数
     * @param target     target
     * @return {@link Object}
     */
    private void executeEvict(CacheEvict cacheEvict, Method method, Object[] args, Object target) {
        // 删除缓存
        // 解析SpEL表达式获取cacheName和key
        String[] cacheNames = cacheEvict.cacheNames();
        Assert.notEmpty(cacheEvict.cacheNames(), CACHE_NAME_ERROR_MESSAGE);
        // 判断是否删除所有缓存数据
        if (cacheEvict.allEntries()) {
            // 删除所有缓存数据(清空)
            for (String cacheName : cacheNames) {
                Collection<Cache> caches = cacheManager.getCache(cacheName);
                if (CollectionUtils.isEmpty(caches)) {
                    // 如果没有找到Cache就新建一个默认的
                    Cache cache = cacheManager.getCache(cacheName,
                            new LayeringCacheSetting(new FirstCacheSetting(), new SecondaryCacheSetting(), "默认缓存配置(清除时生成)", cacheEvict.cacheMode()));
                    cache.clear();
                } else {
                    for (Cache cache : caches) {
                        cache.clear();
                    }
                }
            }
        } else {
            // 删除指定key
            delete(cacheNames, cacheEvict.key(), method, args, target);
        }
    }

    /**
     * 删除执行缓存名称上的指定key
     *
     * @param cacheNames 缓存名称
     * @param keySpEL    key的SpEL表达式
     * @param method     {@link Method}
     * @param args       参数列表
     * @param target     目标类
     */
    private void delete(String[] cacheNames, String keySpEL, Method method, Object[] args, Object target) {
        Object key = generateKey(keySpEL, method, args, target);
        Assert.notNull(key, String.format(CACHE_KEY_ERROR_MESSAGE, keySpEL));
        for (String cacheName : cacheNames) {
            Collection<Cache> caches = cacheManager.getCache(cacheName);
            if (CollectionUtils.isEmpty(caches)) {
                // 如果没有找到Cache就新建一个默认的
                Cache cache = cacheManager.getCache(cacheName,
                        new LayeringCacheSetting(new FirstCacheSetting(), new SecondaryCacheSetting(), "默认缓存配置(删除时生成)", CacheMode.ALL));
                cache.evict(ToStringUtils.toString(key));
            } else {
                for (Cache cache : caches) {
                    cache.evict(ToStringUtils.toString(key));
                }
            }
        }
    }

    /**
     * 执行 CachePut 切面
     *
     * @param result   执行方法的返回值
     * @param cachePut {@link CachePut}
     * @param method   {@link Method}
     * @param args     注解方法参数
     * @param target   target
     */
    private void executePut(Object result, CachePut cachePut, Method method, Object[] args, Object target) throws Throwable {

        String[] cacheNames = cachePut.cacheNames();
        Assert.notEmpty(cachePut.cacheNames(), CACHE_NAME_ERROR_MESSAGE);
        // 解析SpEL表达式获取 key
        Object key = generateKey(cachePut.key(), method, args, target);
        Assert.notNull(key, String.format(CACHE_KEY_ERROR_MESSAGE, cachePut.key()));

        // 从解决中获取缓存配置
        FirstCache firstCache = cachePut.firstCache();
        SecondaryCache secondaryCache = cachePut.secondaryCache();
        FirstCacheSetting firstCacheSetting = new FirstCacheSetting(firstCache.initialCapacity(), firstCache.maximumSize(),
                firstCache.expireTime(), firstCache.timeUnit(), firstCache.expireMode());

        SecondaryCacheSetting secondaryCacheSetting = secondaryCacheHandler(secondaryCache);

        LayeringCacheSetting layeringCacheSetting = new LayeringCacheSetting(firstCacheSetting, secondaryCacheSetting,
                cachePut.depict(), cachePut.cacheMode());


        for (String cacheName : cacheNames) {
            // 通过cacheName和缓存配置获取Cache
            Cache cache = cacheManager.getCache(cacheName, layeringCacheSetting);
            cache.put(ToStringUtils.toString(key), result);
        }
    }

    private Callable getCacheOperationInvoker(ProceedingJoinPoint joinPoint) {
        return () -> {
            try {
                return joinPoint.proceed();
            } catch (Throwable ex) {
                throw (Exception) ex;
            }
        };
    }

    /**
     * 解析SpEL表达式,获取注解上的key属性值
     *
     * @return Object
     */
    private Object generateKey(String keySpEl, Method method, Object[] args, Object target) {

        // 获取注解上的key属性值
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
        if (StringUtils.hasText(keySpEl)) {
            EvaluationContext evaluationContext = evaluator.createEvaluationContext(method, args, target,
                    targetClass, CacheOperationExpressionEvaluator.NO_RESULT);

            AnnotatedElementKey methodCacheKey = new AnnotatedElementKey(method, targetClass);
            // 兼容传null值得情况
            Object keyValue = evaluator.key(keySpEl, methodCacheKey, evaluationContext);
            return Objects.isNull(keyValue) ? "null" : keyValue;
        }
        return this.keyGenerator.generate(target, method, args);
    }

    /**
     * 获取Method
     *
     * @param pjp ProceedingJoinPoint
     * @return {@link Method}
     */
    private Method getSpecificmethod(ProceedingJoinPoint pjp) {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        // The method may be on an interface, but we need attributes from the
        // target class. If the target class is null, the method will be
        // unchanged.
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(pjp.getTarget());
        if (targetClass == null && pjp.getTarget() != null) {
            targetClass = pjp.getTarget().getClass();
        }
        Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
        // If we are dealing with method with generic parameters, find the
        // original method.
        specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
        return specificMethod;
    }

    /**
     * 二级缓存中增加有效时间浮动策略防雪崩
     *
     * @param secondaryCache
     * @return SecondaryCacheSetting
     */
    private SecondaryCacheSetting secondaryCacheHandler(SecondaryCache secondaryCache) {
        SecondaryCacheSetting secondaryCacheSetting = new SecondaryCacheSetting();
        secondaryCacheSetting.setPreloadTime(secondaryCache.preloadTime());
        secondaryCacheSetting.setAllowNullValue(secondaryCache.isAllowNullValue());
        secondaryCacheSetting.setForceRefresh(secondaryCache.forceRefresh());
        secondaryCacheSetting.setMagnification(secondaryCache.magnification());

        TimeUnit timeUnit = secondaryCache.timeUnit(); // 缓存有效时间单位
        long floatTime = secondaryCache.floatTime(); // 缓存浮动时间

        if (floatTime > 0) { // 如果设置了浮动时间
            TimeUnit floatTimeUnit = secondaryCache.floatTimeUnit(); // 浮动时间单位
            long expireTime = secondaryCache.expireTime(); // 有效时间
            if (floatTimeUnit.compareTo(timeUnit) <= 0) { // 浮动时间单位小于等于有效时间单位
                TimeUnit tempTimeUtil = floatTimeUnit;
                if (floatTimeUnit.compareTo(TimeUnit.SECONDS) > 0) { // 如果浮动时间单位大于秒,为了随机均匀一些,转成秒操作
                    tempTimeUtil = TimeUnit.SECONDS;
                    expireTime = TimeUnit.SECONDS.convert(expireTime, timeUnit);
                    floatTime = TimeUnit.SECONDS.convert(floatTime, floatTimeUnit);
                } else {
                    expireTime = floatTimeUnit.convert(expireTime, timeUnit);
                }
                expireTime = ThreadLocalRandom.current().nextLong(expireTime, expireTime + floatTime + 1L);
                secondaryCacheSetting.setExpiration(expireTime);
                secondaryCacheSetting.setTimeUnit(tempTimeUtil);
            } else {
                log.error("float time unit {} more than expire time unit {} !", floatTimeUnit, timeUnit);
                throw new RuntimeException("float time unit more than expire time unit !");
            }
        } else if (secondaryCache.isPreventAvalanche()) { // 启用内置防雪崩策略
            switch (timeUnit) {
                case DAYS: // 有效时间单位为天时,进行DAYS_FLOAT_HOURS个小时(DAYS_FLOAT_HOURS * 3600s)的随机浮动操作
                    long day_expireTimeSeconds = TimeUnit.SECONDS.convert(secondaryCache.expireTime(), timeUnit);
                    day_expireTimeSeconds = ThreadLocalRandom.current().nextLong(day_expireTimeSeconds, day_expireTimeSeconds + DAYS_FLOAT_HOURS * 60 * 60L + 1);

                    secondaryCacheSetting.setExpiration(day_expireTimeSeconds);
                    secondaryCacheSetting.setTimeUnit(TimeUnit.SECONDS);
                    break;
                case HOURS: // 有效时间单位为小时时,进行HOURS_FLOAT_MINUTES分钟(HOURS_FLOAT_MINUTES * 60s)的随机浮动操作
                    long hour_expireTimeSeconds = TimeUnit.SECONDS.convert(secondaryCache.expireTime(), timeUnit);
                    hour_expireTimeSeconds = ThreadLocalRandom.current().nextLong(hour_expireTimeSeconds, hour_expireTimeSeconds + HOURS_FLOAT_MINUTES * 60L + 1);

                    secondaryCacheSetting.setExpiration(hour_expireTimeSeconds);
                    secondaryCacheSetting.setTimeUnit(TimeUnit.SECONDS);
                    break;
                default:
                    break;
            }
        } else { // 如果进行有效时间浮动,则进行默认配置
            secondaryCacheSetting.setExpiration(secondaryCache.expireTime());
            secondaryCacheSetting.setTimeUnit(timeUnit);
        }

        return secondaryCacheSetting;
    }

}

class EmptyObject {
    private static final EmptyObject EMPTY_OBJECT = new EmptyObject();

    private EmptyObject() {
    }

    //获取唯一可用的对象
    public static EmptyObject getInstance() {
        return EMPTY_OBJECT;
    }

}

至此,在二级缓存注解上面添加浮动时间的功能就完成了,使用二级缓存时,可以设置固定的有效时间,也可以使用内置的防雪崩策略,同时也支持自身定义浮动时候,防止缓存大面积同时失效。

你可能感兴趣的:(springboot,java,java,web,缓存雪崩,多级缓存,layering-cache,redis缓存,spring多级缓存)