Commit 612a46be by 仲光辉

feat: add customer annotation cache reference.

parent d2d81138
......@@ -102,7 +102,7 @@ cache: `redis`
}.getType());
} else {
// 数据没有缓存
// TODO 缓存击穿 分布式锁 etc. Redisson
// TODO 缓存击穿 分布式锁 etc. Redisson 可以参阅 cn.dankal.share.cache.reference
Provinces provinces = this.getProvincesByProvinces(provincesId);
Optional<Provinces> provincesOptional;
if (BeanUtil.isNotEmpty(provinces)) {
......@@ -276,6 +276,10 @@ url: GET http://localhost:9102/test/440000
#### 小示例
##### 第三方依赖
> [hutool-bloomFilter](https://mvnrepository.com/artifact/cn.hutool/hutool-bloomFilter)
##### 数据初始化
```java
......@@ -310,7 +314,7 @@ url: GET http://localhost:9102/test/440000
##### 小提示
> 示例 只是单纯的对 `BitMapBloomFilter`进行初数化,我们还需考虑其数据更新.我们可以在 Provinces进行增删的时候手动对 BitMapBloomFilter`进行更新。当然我们也可以通过订阅 诸如`TiDB`的`Ticdc`来实现`BitMapBloomFilter`的数据更新
> 示例 只是单纯的对 `BitMapBloomFilter`进行初数化,我们还需考虑其数据更新.我们可以在 Provinces进行增删的时候手动对 `BitMapBloomFilter`进行更新。当然我们也可以通过订阅 诸如`TiDB`的`Ticdc`来实现`BitMapBloomFilter`的数据更新
### 解决方案示例
......
package cn.dankal.share.cache.reference.cache;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
/**
* 条件缓存
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-03-19
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheCondition {
/**
* Object条件 class
* <pre>
* 如果条件是基本数据类型,则直接顺序指定 {@link #conditionsValues()} 即可
* </pre>
*/
Class<?> objectConditionClass() default Object.class;
/**
* 条件对象所在方法参数的索引
*
*/
int objectConditionParameterIndex() default 0;
/**
* 条件 属性名称 {@link Field#getName()}
* <pre>
* 元素顺序需要和 {@link #conditionsValues()} 保持一致
* 如果条件是基本数据类型,则直接顺序指定 {@link #conditionsValues()} 即可
* </pre>
*/
String[] conditionsFieldNames() default {};
/**
* 条件属性值 {@link Field#get(Object)}
* <pre>
* 当使用 {@link #objectConditionClass()} 时 元素顺序需要和 {@link #conditionsFieldNames()} 保持一致
* 如果是基本数据类型,则只需要和方法参数保持一致即可.
* </pre>
*/
String[] conditionsValues();
}
package cn.dankal.share.cache.reference.cache;
import cn.hutool.core.util.StrUtil;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 对象缓存key: 缓存key 的构成需要从对象中获取时,使用当前注解
* 此注解的对于指定 key 的优先级高于 {@link RedisCache#cacheKey()} {@link RedisCache#dynamicCacheKeyPattern()}
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-03-19
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ObjectCacheKey {
/**
* 对象 类型
*/
Class<?> objectClass();
/**
* 对象所在参数索引
*/
int objectParameterIndex() default 0;
/**
* 动态缓存 key pattern
* <pre>
* 动态 缓存 key pattern {@link StrUtil#format(CharSequence, Object...)}
* 占位符顺序需要和 {@link #fieldNames()} 一致
* </pre>
*/
String dynamicCacheKeyPattern();
/**
* 参与动态缓存 key 构成的属性名称
* <pre>
* 顺序需要和 {@link #dynamicCacheKeyPattern()} 保持一致
* </pre>
*/
String[] fieldNames();
}
package cn.dankal.share.cache.reference.cache;
import cn.hutool.core.util.StrUtil;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* ObjectDistributeLockKey 分布式锁动态key: pattern value 从对象中获取
* 此注解的对于指定 key 的优先级高于 {@link RedisCache#distributedLockKey()} ()} {@link RedisCache#dynamicDistributedLockKeyPattern()}
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021/03/21
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ObjectDistributeLockKey {
/**
* 对象 类型
*/
Class<?> objectClass();
/**
* 对象所在参数索引
*/
int objectParameterIndex() default 0;
/**
* 动态缓存 key pattern
* <pre>
* 动态 缓存 key pattern {@link StrUtil#format(CharSequence, Object...)}
* 占位符顺序需要和 {@link #fieldNames()} 一致
* </pre>
*/
String dynamicDistributeLockKeyPattern();
/**
* 参与动态缓存 key 构成的属性名称
* <pre>
* 顺序需要和 {@link #dynamicDistributeLockKeyPattern()} 保持一致
* </pre>
*/
String[] fieldNames();
}
package cn.dankal.share.cache.reference.cache;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* redis 缓存注解
* <p>
* 在需要使用 redis 缓存的 方法上 使用 当前注解
* </p>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-02-26
* @see RedisCacheAspect
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCache {
/**
* 缓存 key
* <p>
* dynamicCacheKey=false 时,直接使用 cacheKey 作为缓存可以
* </p>
*
* @return 缓存 key
*/
String cacheKey() default "";
/**
* 是否使用 动态 缓存key
* <p>
* etc. product_item_13245253625232
* </p>
*
* @return 是否使用 动态 缓存key
*/
boolean dynamicCacheKey() default true;
/**
* 动态 缓存 key pattern etc. "product_item_{}"
* <pre>
* dynamicCacheKey=true
* 具体请参阅
* cn.hutool.core.util.StrUtil#format(java.lang.CharSequence, java.lang.Object...)
* </pre>
*
* @return 动态 缓存 key pattern etc. "product_item_{}"
*/
String dynamicCacheKeyPattern() default "";
/**
* 动态缓存 key 动态值所在方法参数的位置索引
* <pre>
* {@code
* dynamicCacheKey=true
* etc.
*
* dynamicCacheKeyParameterIndexArray = {0}
* dynamicCacheKeyPattern=product_item_{}
*
* public Product getProduct(Integer productId,...){
* ... ...
* }
*
* getProduct(1324536795953)
* the full cache key : product_item_1324536795953
* }
* </pre>
*
* @return 动态缓存 key 动态值所在方法参数的位置索引
*/
int[] dynamicCacheKeyParameterIndexArray() default {0};
/**
* 缓存 过期时间
* <p>
* 如果 cacheTimeout 小于等于0 则表示缓存属于永久缓存
* <br/>
* 当且仅当 cacheTimeout 的值 大于 0 时 当前配置才生效
* </p>
*
* @return 缓存 过期时间
*/
long cacheTimeout() default 0;
/**
* 缓存过期时间单位
*
* @return 缓存过期时间单位
*/
TimeUnit cacheTimeoutUnit() default TimeUnit.MINUTES;
/**
* 为了避免缓存穿透,我们需要对空值进行缓存
* 空值缓存 过期时间 (单位: 分钟)
*
* @return 空值缓存 过期时间
*/
long emptyObjectCacheTimeout() default 5;
/**
* 是否使用分布式锁
*
* @return 是否使用分布式锁
*/
boolean useDistributeLock() default true;
/**
* 分布式锁 key
* <p>
* 当且仅当 <code>useDistributeLock=true</code>时,当前配置才会生效(使用)
* <br/>
* distributedLockKey 不能为空
* </p>
*
* @return 分布式锁 key
*/
String distributedLockKey() default "";
/**
* 是否使用 动态 distributedLockKey
* <pre>
* distributedLockKey=false
* 直接使用 distributedLockKey 作为 分布式锁 的 key
*
* distributedLockKey=true
* dynamicDistributedLockKeyPattern 和 dynamicDistributedLockKeyParameterIndexArray 动态生成分布式锁 的 key
* </pre>
*
* @return boolean
*/
boolean dynamicDistributedLockKey() default true;
/**
* 动态 分布式锁 key pattern etc. "product_item_{}"
* <pre>
* dynamicDistributedLockKey=true
* cn.hutool.core.util.StrUtil#format(java.lang.CharSequence, java.lang.Object...)
* </pre>
*
* @return 动态 分布式锁 key pattern etc. "product_item_{}"
*/
String dynamicDistributedLockKeyPattern() default "";
/**
* 动态分布式锁 key 动态值所在方法参数的位置索引 数组
* <pre>
* <text>
* dynamicDistributedLockKey=true
* etc.
* dynamicDistributedLockKeyParameterIndexArray ={0}
* dynamicDistributedLockKeyPattern=product_item_{}
* {@code
* public Product getProduct(Integer productId,...){
* ... ...
* }
* }
* getProduct(1324536795953)
* the full distributed lock key : product_item_1324536795953
* </text>
* </pre>
*
* @return 动态分布式锁 key 动态值所在方法参数的位置索引 数组
*/
int[] dynamicDistributedLockKeyParameterIndexArray() default {0};
/**
* 尝试获取 redisson lock 等待的时间
* <p>
* 当且仅当 <code>isUseDistributeLock=true</code>时,当前配置才会生效(使用)
* <br/>
* 此值只用于避免 redis 服务器网络延迟,不易设置过大 切切
* </p>
*
* @return 尝试获取 redisson lock 等待的时间
*/
long redissonTryLockWaitTime() default 120;
/**
* 持有 redisson lock 的时间
* <p>
* 当且仅当 <code>isUseDistributeLock=true</code>时,当前配置才会生效(使用)
* </p>
*
* @return 持有 redisson lock 的时间
*/
long redissonLockLeaseTime() default 230;
/**
* redisson lock 时间单位
* <p>
* <text>
* redissonTryLockWaitTime
* redissonLockLeaseTime
* </text>
* </p>
*
* @return redisson lock 时间单位
*/
TimeUnit redissonLockTimeunit() default TimeUnit.MILLISECONDS;
}
package cn.dankal.share.cache.reference.cache;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import com.google.gson.Gson;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021-02-26
*/
@Slf4j
@Aspect
@Component
public class RedisCacheAspect {
/**
* RedissonClient
*/
private final RedissonClient redissonClient;
/**
* RedisTemplate
*/
private final StringRedisTemplate stringRedisTemplate;
/**
* hutool string format placeholder: {}
*/
private final static String HUTOOL_FORMAT_PLACE_HOLDER = "{}";
/**
* 自旋尝试的最长时间 单位: 毫秒 SECOND default: 300 秒
*/
@Setter
@Getter
@Value("${dankal.distribute.cache.spin-sleep-time-in-second:300}")
private Integer spinTryTimeInSecond;
/**
* 1000
*/
private final static long THOUSANDS_OF_ONE = 1000;
/**
* 防止缓存雪崩,缓存有效期需要加上的时间随机数基数 时间单位为 SECOND
*
* <pre>
* {@code
* ...
* // 缓存过期时间
* long cacheTimeout=100;
* // 缓存雪崩随机基数
* Integer avalancheCardinal=6;
* // 缓存 key
* cacheKey=dankal_cache_example_1489843895849
* // 缓存内容
* cacheContent={... ...}
* ...
* 缓存有效期将会设置为:
* cacheTimeout=RandomUtil.randomInt(avalancheCardinal)+cacheTimeout;
*
* redisTemplate.opsForValue().set(cacheKey,cacheContent,cacheTimeout,TimeUnit.MINUTES)
* }
* </pre>
*/
@Setter
@Getter
@Value("${dankal.distribute.cache.avalanche-random-cardinal:30}")
private Integer avalancheRandomCardinal;
/**
* gson instance
*/
private final Gson gson;
public RedisCacheAspect(RedissonClient redissonClient, StringRedisTemplate stringRedisTemplate, Gson gson) {
this.redissonClient = redissonClient;
this.stringRedisTemplate = stringRedisTemplate;
this.gson = gson;
}
@Around("@annotation(cn.dankal.share.cache.reference.cache.RedisCache)")
public Object cacheAroundAdvice(ProceedingJoinPoint point) {
// 环绕通知目标方法 返回值
Object result;
// 获取代理方法的签名信息
MethodSignature signature = (MethodSignature) point.getSignature();
RedisCache redisCache = signature.getMethod().getAnnotation(RedisCache.class);
ObjectCacheKey objectCacheKey = signature.getMethod().getAnnotation(ObjectCacheKey.class);
boolean needCache = this.verifyConditionCache(point);
try {
if (null == redisCache || !needCache) {
if (log.isErrorEnabled() && null == redisCache) {
log.error("进入缓存环绕通知,但是 RedisCache Object is null");
}
return point.proceed(point.getArgs());
}
// 确定 cacheKey
String cacheKey = getCacheKey(point, redisCache, objectCacheKey);
if (log.isDebugEnabled()) {
log.debug("{} 调用方法 {} cacheKey {}", Thread.currentThread().getName(), signature.getName(), cacheKey);
}
// 尝试缓存命中
result = cacheHit(signature, cacheKey, true);
if (result != null) {
if (log.isDebugEnabled()) {
if (log.isDebugEnabled()) {
log.debug("{} 调用方法 {} 命中缓存数据,即将返回响应数据 : {}", Thread.currentThread().getName(), signature.getName(),gson.toJson(result));
}
}
return result;
}
// 缓存未命中
// 是否使用分布式锁
boolean useDistributeLock = redisCache.useDistributeLock();
if (useDistributeLock) {
String distributedLockKey = getDistributedLockKey(point, redisCache);
if (log.isDebugEnabled()) {
log.debug("{} 分布式锁 key {}", Thread.currentThread().getName(), distributedLockKey);
}
// 使用 Redisson 分布式锁 防止缓存击穿
RLock lock = redissonClient.getLock(distributedLockKey);
try {
long waitTime = redisCache.redissonTryLockWaitTime();
long leaseTime = redisCache.redissonLockLeaseTime();
boolean canLock = lock.tryLock(waitTime, leaseTime, redisCache.redissonLockTimeunit());
// 判断是否能够获取锁
if (canLock) {
if (log.isDebugEnabled()) {
log.debug("{} 即将持有锁: {} {}", Thread.currentThread().getName(), leaseTime,
redisCache.redissonLockTimeunit().name());
}
// 双重检索: 防止重复缓存
Object cacheHit = cacheHit(signature, cacheKey, true);
if (null != cacheHit) {
if (log.isDebugEnabled()) {
log.debug("{} 二次检索命中缓存", Thread.currentThread().getName());
}
return cacheHit;
}
// 执行缓存
return doRedisCache(point, signature, redisCache, cacheKey);
} else {
if (log.isDebugEnabled()) {
log.debug("{} 获取分布式锁失败,即将尝试自旋获取缓存数据", Thread.currentThread().getName());
}
// 获取锁失败
return cacheHit(signature, cacheKey, false);
}
} finally {
// while: 防止线程A 释放 线程B 的锁
while (lock.isHeldByCurrentThread()) {
lock.unlock();
if (log.isDebugEnabled()) {
log.debug("{} 成功释放分布式锁", Thread.currentThread().getName());
}
}
}
} else {
if (log.isDebugEnabled()) {
log.warn("{} 不使用分布式锁", Thread.currentThread().getName());
}
// 不使用分布式锁
return doRedisCache(point, signature, redisCache, cacheKey);
}
} catch (Throwable e) {
if (log.isDebugEnabled()) {
log.debug("error method: {}", signature.getName());
}
throw new RuntimeException(e);
}
}
/**
* 验证条件缓存
*
* @param point ProceedingJoinPoint
* @return 如果需要缓存则返回 true 否则返回 false
*/
private boolean verifyConditionCache(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
CacheCondition cacheCondition = signature.getMethod().getAnnotation(CacheCondition.class);
if (null == cacheCondition) {
// 直接缓存 不存在条件混存
return true;
}
Class<?> objectConditionClass = cacheCondition.objectConditionClass();
String[] conditionsFieldNames = cacheCondition.conditionsFieldNames();
if (Object.class.getName().equals(objectConditionClass.getName())) {
// object 是默认值
if (ArrayUtil.isNotEmpty(conditionsFieldNames)) {
// 当 objectConditionClass 取值 为 object 时,conditionsFieldNames 应该为空才合理
String errorMessage = StrUtil.format(
"verifyConditionCache ==> objectConditionClass is {}. but the conditionsFieldNames is not null [{}]",
objectConditionClass.getName(),
conditionsFieldNames
);
log.error("verifyConditionCache ==> {}", errorMessage);
throw new IllegalStateException(errorMessage);
}
// 基本数据类型条件验证
return this.verifyPrimitiveTypeCondition(point, cacheCondition);
} else {
// 对象条件类型验证
return this.verifyObjectCondition(point, cacheCondition);
}
}
private boolean verifyObjectCondition(ProceedingJoinPoint point, CacheCondition cacheCondition) {
Class<?> conditionClass = cacheCondition.objectConditionClass();
int index = cacheCondition.objectConditionParameterIndex();
if (log.isDebugEnabled()) {
log.debug("verifyObjectCondition ==> index: {}", index);
}
Object conditionObject = point.getArgs()[index];
String[] conditionsFieldNames = cacheCondition.conditionsFieldNames();
if (log.isDebugEnabled()) {
log.debug("verifyObjectCondition ==> conditionsFieldNames: {}", ArrayUtil.toString(conditionsFieldNames));
}
String[] conditionsValues = cacheCondition.conditionsValues();
if (log.isDebugEnabled()) {
log.debug("verifyObjectCondition ==> conditionsValues: {}", ArrayUtil.toString(conditionsValues));
}
for (int i = 0; i < conditionsFieldNames.length; i++) {
Field field = ReflectUtil.getField(conditionClass, conditionsFieldNames[i]);
Object fieldValue = ReflectUtil.getFieldValue(conditionObject, field);
if (!(fieldValue instanceof String)) {
fieldValue = String.valueOf(fieldValue);
}
if (ObjectUtil.notEqual(fieldValue, conditionsValues[i])) {
if (log.isDebugEnabled()) {
log.debug("verifyObjectCondition ==> {} not equals {}", fieldValue, conditionsValues[i]);
log.debug("verifyObjectCondition ==> false");
}
return false;
}
}
if (log.isDebugEnabled()) {
log.debug("verifyObjectCondition ==> true");
}
return true;
}
private boolean verifyPrimitiveTypeCondition(ProceedingJoinPoint point, CacheCondition cacheCondition) {
String[] conditionsValues = cacheCondition.conditionsValues();
Object[] args = point.getArgs();
if (log.isDebugEnabled()) {
log.debug("primitiveTypeCondition ==> conditionsValues:{} args:{}", ArrayUtil.toString(conditionsValues), ArrayUtil.toString(args));
}
for (int i = 0; i < conditionsValues.length; i++) {
if (ObjectUtil.notEqual(conditionsValues[i], args[i])) {
if (log.isDebugEnabled()) {
log.debug("primitiveTypeCondition ==> {} not equals {}", args[i], conditionsValues[i]);
log.debug("primitiveTypeCondition ==> false");
}
return false;
}
}
if (log.isDebugEnabled()) {
log.debug("primitiveTypeCondition ==> true");
}
return true;
}
/**
* 获取 分布式锁 key
*
* @param point ProceedingJoinPoint
* @param redisCache RedisCache
* @return 分布式锁 key
*/
private String getDistributedLockKey(ProceedingJoinPoint point, RedisCache redisCache) {
MethodSignature signature = (MethodSignature) point.getSignature();
ObjectDistributeLockKey objectDistributeLockKey = signature.getMethod().getAnnotation(ObjectDistributeLockKey.class);
if (null != objectDistributeLockKey) {
// object distribute lock key 优先级高于 redisCache
return this.getDynamicObjectDistributeLockKey(point, objectDistributeLockKey);
}
String distributedLockKey;
// 获取 分布式锁 的 key
if (redisCache.dynamicDistributedLockKey()) {
// 动态分布式锁的 key
String dynamicDistributedLockKeyPattern = redisCache.dynamicDistributedLockKeyPattern();
if (StringUtils.isEmpty(dynamicDistributedLockKeyPattern)) {
throw new IllegalArgumentException(
"使用 动态的分布式锁key(dynamicDistributedLockKey=ture),但是未指定 dynamicDistributedLockKeyPattern");
}
if (!dynamicDistributedLockKeyPattern.contains(HUTOOL_FORMAT_PLACE_HOLDER)) {
throw new IllegalArgumentException(
"dynamicDistributedLockKeyPattern 必须含有{} 占位符.详情请参阅: cn.hutool.core.util.StrUtil.format(java.lang.CharSequence, java.lang.Object...)");
}
int[] dynamicDistributedLockKeyParameterIndexArray =
redisCache.dynamicDistributedLockKeyParameterIndexArray();
Object[] patternParameters = new Object[dynamicDistributedLockKeyParameterIndexArray.length];
for (int i = 0; i < dynamicDistributedLockKeyParameterIndexArray.length; i++) {
patternParameters[i] = point.getArgs()[dynamicDistributedLockKeyParameterIndexArray[i]];
}
distributedLockKey = StrUtil.format(dynamicDistributedLockKeyPattern, patternParameters);
} else {
// 直接 指定 分布式锁的 key
distributedLockKey = redisCache.distributedLockKey();
if (StringUtils.isEmpty(distributedLockKey)) {
throw new IllegalStateException(
"未使用 动态的分布式锁key(dynamicDistributedLockKey=false),但是未指定 distributedLockKey");
}
}
return distributedLockKey;
}
private String getDynamicObjectDistributeLockKey(ProceedingJoinPoint point, ObjectDistributeLockKey objectDistributeLockKey) {
Class<?> objectClass = objectDistributeLockKey.objectClass();
String[] fieldNames = objectDistributeLockKey.fieldNames();
Object[] patternValues = new Object[fieldNames.length];
int index = objectDistributeLockKey.objectParameterIndex();
Object object = point.getArgs()[index];
for (int i = 0; i < fieldNames.length; i++) {
Field field = ReflectUtil.getField(objectClass, fieldNames[i]);
Object fieldValue = ReflectUtil.getFieldValue(object, field);
patternValues[i] = fieldValue;
}
if (log.isDebugEnabled()) {
log.debug(
"getDynamicObjectDistributeLockKey ==> dynamicDistributeLockKeyPattern {} patternValues {}",
objectDistributeLockKey.dynamicDistributeLockKeyPattern(),
ArrayUtil.toString(patternValues)
);
}
String dynamicDistributeLockKey = StrUtil.format(objectDistributeLockKey.dynamicDistributeLockKeyPattern(), patternValues);
if (log.isDebugEnabled()) {
log.debug("getDynamicObjectDistributeLockKey ==> dynamicDistributeLockKey {}", dynamicDistributeLockKey);
}
return dynamicDistributeLockKey;
}
/**
* 获取 cache key
*
* @param point ProceedingJoinPoint
* @param redisCache RedisCache
* @param objectCacheKey objectCacheKey
* @return the cache key
*/
private String getCacheKey(ProceedingJoinPoint point, RedisCache redisCache, ObjectCacheKey objectCacheKey) {
if (null != objectCacheKey) {
// 对象缓存key 优先级高于 redisCache 配置的 cacheKey
return getObjectCacheKey(point, objectCacheKey);
}
String cacheKey;
boolean dynamicCacheKey = redisCache.dynamicCacheKey();
if (dynamicCacheKey) {
if (log.isDebugEnabled()) {
log.debug("使用动态缓存key");
}
// 动态 缓存key
String dynamicCacheKeyPattern = redisCache.dynamicCacheKeyPattern();
if (StringUtils.isEmpty(dynamicCacheKeyPattern)) {
throw new IllegalStateException("使用动态缓存key. 但是尚未配置 动态key pattern(dynamicCacheKeyPattern)");
}
if (!dynamicCacheKeyPattern.contains(HUTOOL_FORMAT_PLACE_HOLDER)) {
throw new IllegalArgumentException(
"dynamicCacheKeyPattern 必须含有{} 占位符.详情请参阅: cn.hutool.core.util.StrUtil.format(java.lang.CharSequence, java.lang.Object...)");
}
int[] indexArray = redisCache.dynamicCacheKeyParameterIndexArray();
Object[] patternParameters = new Object[indexArray.length];
for (int i = 0; i < indexArray.length; i++) {
patternParameters[i] = point.getArgs()[indexArray[i]];
}
cacheKey = StrUtil.format(dynamicCacheKeyPattern, patternParameters);
} else {
// 使用指定的 cache key
cacheKey = redisCache.cacheKey();
if (StringUtils.isEmpty(cacheKey)) {
throw new IllegalStateException("未使用动态缓存key,但是亦为指定 cacheKey");
}
}
return cacheKey;
}
private String getObjectCacheKey(ProceedingJoinPoint point, ObjectCacheKey objectCacheKey) {
Class<?> objectClass = objectCacheKey.objectClass();
int index = objectCacheKey.objectParameterIndex();
if (log.isDebugEnabled()) {
log.debug("getObjectCacheKey ==> index {}", index);
}
String[] fieldNames = objectCacheKey.fieldNames();
if (log.isDebugEnabled()) {
log.debug("getObjectCacheKey ==> fieldNames : {}", ArrayUtil.toString(fieldNames));
}
Object object = point.getArgs()[index];
Object[] patternValues = new Object[fieldNames.length];
for (int i = 0; i < fieldNames.length; i++) {
Field field = ReflectUtil.getField(objectClass, fieldNames[i]);
Object fieldValue = ReflectUtil.getFieldValue(object, field);
patternValues[i] = fieldValue;
}
if (log.isDebugEnabled()) {
log.debug("getObjectCacheKey ==> dynamicCacheKeyPattern : {} patternValues: {} ", objectCacheKey.dynamicCacheKeyPattern(), ArrayUtil.toString(patternValues));
}
return StrUtil.format(objectCacheKey.dynamicCacheKeyPattern(), patternValues);
}
/**
* 将执行结果缓存
*
* @param point ProceedingJoinPoint
* @param signature MethodSignature
* @param redisCache RedisCache
* @param cacheKey cache key
* @return 执行结果数据
* @throws Throwable 目标方法执行异常 / signature.getReturnType().newInstance() 执行异常(防止缓存穿透)
*/
private Object doRedisCache(ProceedingJoinPoint point, MethodSignature signature, RedisCache redisCache,
String cacheKey) throws Throwable {
Object result = point.proceed(point.getArgs());
if (null == result) {
// 防止 缓存穿透
Object nullObj = this.createEmptyObject(signature);
stringRedisTemplate.opsForValue().set(cacheKey, gson.toJson(nullObj),
redisCache.emptyObjectCacheTimeout(), TimeUnit.MINUTES);
return nullObj;
}
long cacheTimeout = redisCache.cacheTimeout();
if (cacheTimeout > 0) {
// 缓存具有 TTL
stringRedisTemplate.opsForValue().set(cacheKey, gson.toJson(result), cacheTimeout,
redisCache.cacheTimeoutUnit());
// 防止缓存雪崩
// TTL 可能的单位: day hours minutes ... ...
// 为了屏蔽不同时间单位,先添加缓存,然后统一使用 SECOND 单位获取 缓存过期时间,然后再
// 在原来过期时间的基础上添加随机数,防止缓存雪崩
Long expire = stringRedisTemplate.getExpire(cacheKey, TimeUnit.SECONDS);
if (null == expire) {
throw new IllegalStateException("设置了缓存有效期,但是在做防止缓存雪崩的操作时,`redisTemplate.getExpire`获取过期时间失效");
}
if (log.isDebugEnabled()) {
log.debug("缓存 {} 初始TTL:{} 秒", cacheKey, expire);
}
expire = expire + RandomUtil.randomInt(avalancheRandomCardinal);
if (log.isDebugEnabled()) {
log.debug("缓存 {} 防止缓存雪崩更TTL为:{} 秒", cacheKey, expire);
}
stringRedisTemplate.expire(cacheKey, expire, TimeUnit.SECONDS);
} else {
// 永久缓存
stringRedisTemplate.opsForValue().set(cacheKey, gson.toJson(result));
}
return result;
}
/**
* 缓存命中
*
* @param signature MethodSignature
* @param key cache key
* @param isHit 是否命中缓存
* <p>
* isHit: true 命中缓存 <br/>
* isHit: false 获取 分布式锁失败 使用自旋的方式获取缓存
* </p>
* @return null / 缓存数据 / 自旋上限异常
* @throws IllegalStateException 线程自旋尝试上限(读取缓存)
*/
private Object cacheHit(MethodSignature signature, String key, boolean isHit) throws IllegalStateException {
// 获取数据 redis 的 String数据类型: key,value 都是JSON 字符串
String cache = stringRedisTemplate.opsForValue().get(key);
// 命中缓存
if (!StringUtils.isEmpty(cache)) {
// 有数据 ,则将数据进行转化
return gson.fromJson(cache, TypeUtil.getReturnType(signature.getMethod()));
}
if (isHit) {
// 尝试缓存命中
if (log.isDebugEnabled()) {
log.debug("{} 调用方法 {} 未成功命中缓存 ", Thread.currentThread().getName(), signature.getName());
}
return null;
}
// 未命中缓存,线程进入自旋尝试获取缓存数据
final long startMs = System.currentTimeMillis();
while ((System.currentTimeMillis() - startMs) <= spinTryTimeInSecond * THOUSANDS_OF_ONE) {
String cacheContent = stringRedisTemplate.opsForValue().get(key);
// 命中缓存
if (!StringUtils.isEmpty(cacheContent)) {
if (log.isDebugEnabled()) {
log.debug("{} 调用方法 {} 现在 成功获取到缓存数据", Thread.currentThread().getName(), signature.getName());
}
// 有数据 ,则将数据进行转化
return gson.fromJson(cacheContent, TypeUtil.getReturnType(signature.getMethod()));
}
}
// 自旋上限仍未获取到模板数据
throw new IllegalStateException("执行缓存获取失败[自旋上限],Timeout: " + spinTryTimeInSecond + " 秒");
// throw new SpinTryGetCacheException("执行缓存获取失败[自旋上限]");
}
/**
* 缓存穿透: 空对象创建
*
* @param methodSignature methodSignature
* @return Object
* @throws Exception
*/
private Object createEmptyObject(MethodSignature methodSignature) throws Exception {
String returnTypeName = methodSignature.getReturnType().getName();
if (List.class.getName().equals(returnTypeName)) {
return new ArrayList<>(1);
} else if (Map.class.getName().equals(returnTypeName)) {
return MapUtil.newHashMap(1);
}
return methodSignature.getReturnType().newInstance();
}
}
package cn.dankal.share.cache.reference.config;
import cn.hutool.core.util.StrUtil;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* RedissonBaseConfig
* <br/>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2021/02/26
* @copyright 蛋壳创意科技 - www.dankal.cn
*/
@Configuration
public class RedissonBaseConfig {
/**
* RedisProperties
*/
private final RedisProperties redisProperties;
/**
* redisson redis host prefix
*/
private final static String ADDRESS_PREFIX = "redis://";
public RedissonBaseConfig(RedisProperties redisProperties) {
this.redisProperties = redisProperties;
}
@Bean(destroyMethod ="shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
// 验证是否配置了 redis host/ redis nodes
String redisHost = redisProperties.getHost();
RedisProperties.Cluster propertiesCluster = redisProperties.getCluster();
boolean checkRedisHost = StrUtil.isBlank(redisHost) && (propertiesCluster == null || CollectionUtils.isEmpty(propertiesCluster.getNodes()));
if (checkRedisHost) {
throw new IllegalArgumentException("需要配置 redis.host 或 redis.cluster.nodes");
}
if (null != propertiesCluster && !CollectionUtils.isEmpty(propertiesCluster.getNodes())) {
// redis cluster
config.useClusterServers()
.addNodeAddress(this.transferRedissonRedisNode(propertiesCluster.getNodes()))
.setPassword(redisProperties.getPassword())
.setTimeout((int) redisProperties.getTimeout().toMillis());
} else {
// redis singleton
config.useSingleServer()
.setAddress(ADDRESS_PREFIX + redisHost + ":" + redisProperties.getPort())
.setTimeout((int) redisProperties.getTimeout().toMillis())
.setPassword(redisProperties.getPassword());
}
return Redisson.create(config);
}
/**
* 将 node list 转化为 redisson redis cluster config
* <p>
* 127.0.0.0:6379 --> redis://127.0.0.1:6379
* </p>
*
* @param redisNodeList redisNodeList
* @return string []
*/
private String[] transferRedissonRedisNode(List<String> redisNodeList) {
String[] resultArray = new String[redisNodeList.size()];
for (int i = 0; i < resultArray.length; i++) {
resultArray[i] = ADDRESS_PREFIX + redisNodeList.get(i);
}
return resultArray;
}
}
......@@ -108,7 +108,7 @@ public class IProvincesServiceImpl extends ServiceImpl<ProvincesMapper, Province
}.getType());
} else {
// 数据没有缓存
// TODO 缓存击穿 分布式锁 etc. Redisson
// TODO 缓存击穿 分布式锁 etc. Redisson 可以参阅 cn.dankal.share.cache.reference
Provinces provinces = this.getProvincesByProvinces(provincesId);
Optional<Provinces> provincesOptional;
if (BeanUtil.isNotEmpty(provinces)) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment