最近在项目中有使用layering-cache(https://gitee.com/xiaolyuh/layering-cache?_from=gitee_search)进行多级缓存,有开发人员说想在缓存的时候增加一定时间的随机浮动,已到达防止缓存雪崩的目的,由于layering-cache是开源的,想增加此功能也十分简单,在此记录一下。
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。
本次改造只针对二级缓存,也就是redis缓存层面做代码改造,一级缓存暂时不管,代码实现如下:
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;
}
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;
}
LayeringAspect
切面类中修改二级缓存设置对象的创建方式,代码如下: SecondaryCacheSetting secondaryCacheSetting = secondaryCacheHandler(secondaryCache);
SecondaryCacheSetting secondaryCacheSetting = secondaryCacheHandler(secondaryCache);
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;
}
}
至此,在二级缓存注解上面添加浮动时间的功能就完成了,使用二级缓存时,可以设置固定的有效时间,也可以使用内置的防雪崩策略,同时也支持自身定义浮动时候,防止缓存大面积同时失效。