Skip to content

Commit c482209

Browse files
authored
Merge pull request qiujiayu#73 from zhuangzhuangdashen/feature/lizhuangzhuang_addFixRateRefreshSupport
Feature/lizhuangzhuang add fix rate refresh support
2 parents 32a72a1 + 2635b4e commit c482209

4 files changed

Lines changed: 248 additions & 35 deletions

File tree

pom.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@
164164
<version>2.1</version>
165165
<optional>true</optional>
166166
</dependency>
167-
168167
<dependency>
169168
<groupId>junit</groupId>
170169
<artifactId>junit</artifactId>

src/main/java/com/jarvis/cache/AutoLoadHandler.java

Lines changed: 189 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
import com.jarvis.cache.to.CacheKeyTO;
88
import com.jarvis.cache.to.CacheWrapper;
99
import lombok.extern.slf4j.Slf4j;
10+
import org.apache.commons.lang3.StringUtils;
1011

1112
import java.lang.reflect.Method;
1213
import java.util.Arrays;
13-
import java.util.concurrent.ConcurrentHashMap;
14-
import java.util.concurrent.LinkedBlockingQueue;
15-
import java.util.concurrent.ThreadLocalRandom;
14+
import java.util.Timer;
15+
import java.util.TimerTask;
16+
import java.util.concurrent.*;
1617

1718
/**
1819
* 用于处理自动加载缓存,sortThread 从autoLoadMap中取出数据,然后通知threads进行处理。
@@ -60,6 +61,13 @@ public class AutoLoadHandler {
6061
*/
6162
private final AutoLoadConfig config;
6263

64+
65+
private static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;
66+
67+
public static ScheduledThreadPoolExecutor getScheduledThreadPoolExecutor() {
68+
return scheduledThreadPoolExecutor;
69+
}
70+
6371
/**
6472
* @param cacheHandler 缓存的set,get方法实现类
6573
* @param config 配置
@@ -75,6 +83,8 @@ public AutoLoadHandler(CacheHandler cacheHandler, AutoLoadConfig config) {
7583
this.sortThread = new Thread(new SortRunnable());
7684
this.sortThread.setDaemon(true);
7785
this.sortThread.start();
86+
// init fixed rate refresh process thread pool
87+
initScheduledThreadPoolExecutor();
7888
for (int i = 0; i < this.config.getThreadCnt(); i++) {
7989
this.threads[i] = new Thread(new AutoLoadRunnable());
8090
this.threads[i].setName(THREAD_NAME_PREFIX + i);
@@ -89,6 +99,10 @@ public AutoLoadHandler(CacheHandler cacheHandler, AutoLoadConfig config) {
8999
}
90100
}
91101

102+
private void initScheduledThreadPoolExecutor() {
103+
scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(10);
104+
}
105+
92106
public int getSize() {
93107
if (null != autoLoadMap) {
94108
return autoLoadMap.size();
@@ -146,6 +160,29 @@ public AutoLoadTO putIfAbsent(CacheKeyTO cacheKey, CacheAopProxyChain joinPoint,
146160
if (null == autoLoadMap) {
147161
return null;
148162
}
163+
164+
// 如果fixRateUpdateCache注解字段不为空,则走固定刷新逻辑
165+
if(StringUtils.isNotEmpty(cache.fixRateUpdateCache())) {
166+
AutoLoadTO autoLoadTO = autoLoadMap.get(cacheKey);
167+
if (null != autoLoadTO) {
168+
autoLoadMap.remove(cacheKey);
169+
}
170+
171+
DeepClone deepClone = new DeepClone(joinPoint, cache).invoke();
172+
if (deepClone.is()) return null;
173+
174+
Object[] arguments = deepClone.getArguments();
175+
autoLoadTO = new AutoLoadTO(cacheKey, joinPoint, arguments, cache, Integer.MAX_VALUE);
176+
// 设置过期时间为永不过期
177+
autoLoadTO.setExpire(Integer.MAX_VALUE);
178+
179+
boolean openFixRateUpdateCache = fixRateUpdateCacheIfNeeded(joinPoint.getMethod().getName(), autoLoadTO);
180+
if (openFixRateUpdateCache) {
181+
return null;
182+
}
183+
}
184+
185+
// 走老逻辑-》交由AutoLoad处理
149186
AutoLoadTO autoLoadTO = autoLoadMap.get(cacheKey);
150187
if (null != autoLoadTO) {
151188
return autoLoadTO;
@@ -185,6 +222,120 @@ public AutoLoadTO putIfAbsent(CacheKeyTO cacheKey, CacheAopProxyChain joinPoint,
185222
return null;
186223
}
187224

225+
private boolean fixRateUpdateCacheIfNeeded(String methodName, AutoLoadTO autoLoadTO) {
226+
if (null == autoLoadTO || autoLoadTO.getCache() == null ||
227+
StringUtils.isEmpty(autoLoadTO.getCache().fixRateUpdateCache())) {
228+
return false;
229+
}
230+
231+
// 如果配置固定频率刷新 则缓存有效期为永久
232+
doExecute(methodName, autoLoadTO);
233+
return true;
234+
}
235+
236+
private void doExecute(String methodName, AutoLoadTO autoLoadTO) {
237+
// 解析fixRateUpdateCache Timer表达式
238+
String updateCacheCronExpression = autoLoadTO.getCache().fixRateUpdateCache();
239+
if (!updateCacheCronExpression.contains(",")) {
240+
log.error("不符合规则的频率表达式{}", updateCacheCronExpression);
241+
return;
242+
}
243+
long delay;
244+
long period;
245+
try {
246+
String[] split = updateCacheCronExpression.split(",");
247+
delay = Long.parseLong(split[0]);
248+
period = Long.parseLong(split[1]);
249+
} catch (Exception e) {
250+
log.error("not matched cron expression-{}", updateCacheCronExpression);
251+
return;
252+
}
253+
scheduledThreadPoolExecutor.scheduleWithFixedDelay(new FixRateUpdateCacheTask(autoLoadTO), delay,
254+
period, TimeUnit.SECONDS);
255+
log.info("register fix rate refresh task——method-{}, rate-{}", methodName, updateCacheCronExpression);
256+
}
257+
258+
class FixRateUpdateCacheTask implements Runnable{
259+
private AutoLoadTO autoLoadTO;
260+
261+
public FixRateUpdateCacheTask(AutoLoadTO autoLoadTO) {
262+
this.autoLoadTO = autoLoadTO;
263+
}
264+
265+
@Override
266+
public void run() {
267+
// 执行更新 依然复用"拿来主义"
268+
Cache cache = autoLoadTO.getCache();
269+
log.debug("执行定时刷新缓存任务, {}", cache.fixRateUpdateCache());
270+
CacheWrapper<Object> result = null;
271+
if (config.isCheckFromCacheBeforeLoad()) {
272+
try {
273+
Method method = autoLoadTO.getJoinPoint().getMethod();
274+
result = cacheHandler.get(autoLoadTO.getCacheKey(), method);
275+
} catch (Exception ex) {
276+
log.error(ex.getMessage(), ex);
277+
}
278+
279+
if (null != result) {
280+
autoLoadTO.setExpire(result.getExpire());
281+
if (result.getLastLoadTime() > autoLoadTO.getLastLoadTime()) {
282+
autoLoadTO.setLastLoadTime(result.getLastLoadTime());
283+
return;
284+
}
285+
}
286+
}
287+
CacheAopProxyChain pjp = autoLoadTO.getJoinPoint();
288+
CacheKeyTO cacheKey = autoLoadTO.getCacheKey();
289+
DataLoader dataLoader;
290+
if (config.isDataLoaderPooled()) {
291+
DataLoaderFactory factory = DataLoaderFactory.getInstance();
292+
dataLoader = factory.getDataLoader();
293+
} else {
294+
dataLoader = new DataLoader();
295+
}
296+
CacheWrapper<Object> newCacheWrapper = null;
297+
long loadDataUseTime = 0L;
298+
try {
299+
newCacheWrapper = dataLoader.init(pjp, autoLoadTO, cacheKey, cache, cacheHandler).loadData()
300+
.getCacheWrapper();
301+
loadDataUseTime = dataLoader.getLoadDataUseTime();
302+
} catch (Throwable e) {
303+
log.error(e.getMessage(), e);
304+
} finally {
305+
if (config.isDataLoaderPooled()) {
306+
DataLoaderFactory factory = DataLoaderFactory.getInstance();
307+
factory.returnObject(dataLoader);
308+
}
309+
}
310+
// 如果数据加载失败,则把旧数据进行续租
311+
if (null == newCacheWrapper && null != result) {
312+
newCacheWrapper = new CacheWrapper<Object>(result.getCacheObject(), Integer.MAX_VALUE);
313+
}
314+
writeCacheAndSetLoadTime(cache, pjp, cacheKey, newCacheWrapper, loadDataUseTime, autoLoadTO);
315+
}
316+
}
317+
318+
/**
319+
* 写入缓存并且设置上一次加载时间
320+
* @param cache
321+
* @param pjp
322+
* @param cacheKey
323+
* @param newCacheWrapper
324+
* @param loadDataUseTime
325+
* @param autoLoadTO
326+
*/
327+
private void writeCacheAndSetLoadTime(Cache cache, CacheAopProxyChain pjp, CacheKeyTO cacheKey, CacheWrapper<Object> newCacheWrapper, long loadDataUseTime, AutoLoadTO autoLoadTO) {
328+
try {
329+
if (null != newCacheWrapper) {
330+
cacheHandler.writeCache(pjp, autoLoadTO.getArgs(), cache, cacheKey, newCacheWrapper);
331+
autoLoadTO.setLastLoadTime(newCacheWrapper.getLastLoadTime())
332+
.setExpire(newCacheWrapper.getExpire()).addUseTotalTime(loadDataUseTime);
333+
}
334+
} catch (Exception e) {
335+
log.error(e.getMessage(), e);
336+
}
337+
}
338+
188339
/**
189340
* 获取自动加载队列,如果是web应用,建议把自动加载队列中的数据都输出到页面中,并增加一些管理功能。
190341
*
@@ -252,7 +403,6 @@ public void run() {
252403
}
253404

254405
class AutoLoadRunnable implements Runnable {
255-
256406
@Override
257407
public void run() {
258408
while (running) {
@@ -372,17 +522,46 @@ private void loadCache(AutoLoadTO autoLoadTO) {
372522
int newExpire = AUTO_LOAD_MIN_EXPIRE + 60;
373523
newCacheWrapper = new CacheWrapper<Object>(result.getCacheObject(), newExpire);
374524
}
525+
writeCacheAndSetLoadTime(cache, pjp, cacheKey, newCacheWrapper, loadDataUseTime, autoLoadTO);
526+
}
527+
}
528+
}
529+
530+
private class DeepClone {
531+
private boolean myResult;
532+
private CacheAopProxyChain joinPoint;
533+
private Cache cache;
534+
private Object[] arguments;
535+
536+
public DeepClone(CacheAopProxyChain joinPoint, Cache cache) {
537+
this.joinPoint = joinPoint;
538+
this.cache = cache;
539+
}
540+
541+
boolean is() {
542+
return myResult;
543+
}
544+
545+
public Object[] getArguments() {
546+
return arguments;
547+
}
548+
549+
public DeepClone invoke() {
550+
if (cache.argumentsDeepcloneEnable()) {
375551
try {
376-
if (null != newCacheWrapper) {
377-
cacheHandler.writeCache(pjp, autoLoadTO.getArgs(), cache, cacheKey, newCacheWrapper);
378-
autoLoadTO.setLastLoadTime(newCacheWrapper.getLastLoadTime())
379-
.setExpire(newCacheWrapper.getExpire()).addUseTotalTime(loadDataUseTime);
380-
}
552+
// 进行深度复制
553+
arguments = (Object[]) cacheHandler.getCloner().deepCloneMethodArgs(joinPoint.getMethod(),
554+
joinPoint.getArgs());
381555
} catch (Exception e) {
382556
log.error(e.getMessage(), e);
557+
myResult = true;
558+
return this;
383559
}
560+
} else {
561+
arguments = joinPoint.getArgs();
384562
}
563+
myResult = false;
564+
return this;
385565
}
386566
}
387-
388567
}

src/main/java/com/jarvis/cache/DataLoader.java

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import com.jarvis.cache.to.ProcessingTO;
1212
import lombok.extern.slf4j.Slf4j;
1313

14+
import java.util.concurrent.ScheduledThreadPoolExecutor;
15+
1416
/**
1517
* 数据加载器
1618
*
@@ -139,36 +141,51 @@ public DataLoader loadData() throws Throwable {
139141
private void doFirstRequest(ProcessingTO processingTO) throws Throwable {
140142
ILock distributedLock = cacheHandler.getLock();
141143
// 开启分布式锁
142-
if (null != distributedLock && cache.lockExpire() > 0) {
143-
String lockKey = cacheKey.getLockKey();
144-
long startWait = processingTO.getStartTime();
145-
do {
146-
// 获得分布式锁
147-
if (distributedLock.tryLock(lockKey, cache.lockExpire())) {
148-
try {
149-
getData();
150-
} finally {
151-
distributedLock.unlock(lockKey);
144+
Throwable throwable = null;
145+
try {
146+
if (null != distributedLock && cache.lockExpire() > 0) {
147+
String lockKey = cacheKey.getLockKey();
148+
long startWait = processingTO.getStartTime();
149+
do {
150+
// 获得分布式锁
151+
if (distributedLock.tryLock(lockKey, cache.lockExpire())) {
152+
try {
153+
getData();
154+
} finally {
155+
distributedLock.unlock(lockKey);
156+
}
157+
break;
158+
}
159+
int tryCnt = 20;
160+
// 没有获得锁时,定时缓存尝试获取数据
161+
for (int i = 0; i < tryCnt; i++) {
162+
cacheWrapper = cacheHandler.get(cacheKey, pjp.getMethod());
163+
if (null != cacheWrapper) {
164+
break;
165+
}
166+
Thread.sleep(10);
152167
}
153-
break;
154-
}
155-
int tryCnt = 20;
156-
// 没有获得锁时,定时缓存尝试获取数据
157-
for (int i = 0; i < tryCnt; i++) {
158-
cacheWrapper = cacheHandler.get(cacheKey, pjp.getMethod());
159168
if (null != cacheWrapper) {
160169
break;
161170
}
162-
Thread.sleep(10);
163-
}
164-
if (null != cacheWrapper) {
165-
break;
171+
} while (System.currentTimeMillis() - startWait < cache.waitTimeOut());
172+
if (null == cacheWrapper) {
173+
throw new LoadDataTimeOutException("load data for key \"" + cacheKey.getCacheKey() + "\" timeout(" + cache.waitTimeOut() + " ms).");
166174
}
167-
} while (System.currentTimeMillis() - startWait < cache.waitTimeOut());
168-
if (null == cacheWrapper) {
169-
throw new LoadDataTimeOutException("load data for key \"" + cacheKey.getCacheKey() + "\" timeout(" + cache.waitTimeOut() + " ms).");
170175
}
171-
} else {
176+
} catch (Throwable e) {
177+
if (cache.openLockDown()) {
178+
throwable = e;
179+
// 关闭分布式锁
180+
cacheHandler.setLock(null);
181+
log.error("分布式锁异常,强制停止使用分布式锁!", throwable);
182+
} else {
183+
// 否则抛异常
184+
log.error("分布式锁异常!", e);
185+
throw e;
186+
}
187+
}
188+
if (throwable != null) {
172189
getData();
173190
}
174191
// 本地缓存

src/main/java/com/jarvis/cache/annotation/Cache.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.jarvis.cache.annotation;
22

33
import com.jarvis.cache.type.CacheOpType;
4+
import lombok.Setter;
45

56
import java.lang.annotation.Documented;
67
import java.lang.annotation.ElementType;
@@ -71,6 +72,16 @@
7172
*/
7273
boolean autoload() default false;
7374

75+
/**
76+
* 以固定频率的方式刷新缓存 (补充expire无限大时无法依靠alarmTime频繁刷新缓存的不足)
77+
* 格式 “initDelay,period” 默认单位s 【初始延迟时长,执行周期】
78+
* 如【5,10】意为5s后开始以10s为周期执行刷新任务
79+
* 为了向后兼容, 当alarmTime存在时优先解析alarmTime
80+
* 暂不支持自定义+扩展
81+
* @return 固定表达式 “initDelay,period”
82+
*/
83+
String fixRateUpdateCache() default "";
84+
7485
/**
7586
* 自动缓存的条件,可以为空,返回 true 或者 false,如果设置了此值,autoload() 就失效,例如:null !=
7687
* #args[0].keyword,当第一个参数的keyword属性为null时设置为自动加载。
@@ -122,6 +133,13 @@
122133
*/
123134
int lockExpire() default 10;
124135

136+
/**
137+
* 是否开启锁降级
138+
* 默认不开启;
139+
* 如果开启,当分布式锁抛异常时不使用分布式锁
140+
*/
141+
boolean openLockDown() default false;
142+
125143
/**
126144
* 是否打开对参数进行深度复制,默认是true,是为了避免外部改变参数值。如果确保不被修改,最好是设置为false,这样性能会更高。
127145
*

0 commit comments

Comments
 (0)