Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
dankal-share-cache
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
仲光辉
dankal-share-cache
Commits
612a46be
Commit
612a46be
authored
Mar 27, 2021
by
仲光辉
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add customer annotation cache reference.
parent
d2d81138
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
977 additions
and
3 deletions
+977
-3
README.md
README.md
+6
-2
CacheCondition.java
...cn/dankal/share/cache/reference/cache/CacheCondition.java
+52
-0
ObjectCacheKey.java
...cn/dankal/share/cache/reference/cache/ObjectCacheKey.java
+49
-0
ObjectDistributeLockKey.java
.../share/cache/reference/cache/ObjectDistributeLockKey.java
+47
-0
RedisCache.java
...ava/cn/dankal/share/cache/reference/cache/RedisCache.java
+207
-0
RedisCacheAspect.java
.../dankal/share/cache/reference/cache/RedisCacheAspect.java
+533
-0
RedissonBaseConfig.java
...nkal/share/cache/reference/config/RedissonBaseConfig.java
+82
-0
IProvincesServiceImpl.java
...ankal/share/cache/service/impl/IProvincesServiceImpl.java
+1
-1
No files found.
README.md
View file @
612a46be
...
...
@@ -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`的数据更新
### 解决方案示例
...
...
src/main/java/cn/dankal/share/cache/reference/cache/CacheCondition.java
0 → 100644
View file @
612a46be
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
();
}
src/main/java/cn/dankal/share/cache/reference/cache/ObjectCacheKey.java
0 → 100644
View file @
612a46be
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
();
}
src/main/java/cn/dankal/share/cache/reference/cache/ObjectDistributeLockKey.java
0 → 100644
View file @
612a46be
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
();
}
src/main/java/cn/dankal/share/cache/reference/cache/RedisCache.java
0 → 100644
View file @
612a46be
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
;
}
src/main/java/cn/dankal/share/cache/reference/cache/RedisCacheAspect.java
0 → 100644
View file @
612a46be
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
();
}
}
src/main/java/cn/dankal/share/cache/reference/config/RedissonBaseConfig.java
0 → 100644
View file @
612a46be
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
;
}
}
src/main/java/cn/dankal/share/cache/service/impl/IProvincesServiceImpl.java
View file @
612a46be
...
...
@@ -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
))
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment