一、自动刷新实现
这里贴出的代码是添加刷新任务的方法,首先维护了一个任务队列,以taskId作为key,RefreshTask作为value。
private ConcurrentHashMap<Object, RefreshTask> taskMap = new ConcurrentHashMap<>();
protected void addOrUpdateRefreshTask(K key, CacheLoader<K,V> loader) {
RefreshPolicy refreshPolicy = config.getRefreshPolicy();
if (refreshPolicy == null) {
return;
}
long refreshMillis = refreshPolicy.getRefreshMillis();
if (refreshMillis > 0) {
Object taskId = getTaskId(key);
RefreshTask refreshTask = taskMap.computeIfAbsent(taskId, tid -> {
logger.debug("add refresh task. interval={}, key={}", refreshMillis , key);
RefreshTask task = new RefreshTask(taskId, key, loader);
task.lastAccessTime = System.currentTimeMillis();
ScheduledFuture<?> future = JetCacheExecutor.heavyIOExecutor().scheduleWithFixedDelay(
task, refreshMillis, refreshMillis, TimeUnit.MILLISECONDS);
task.future = future;
return task;
});
refreshTask.lastAccessTime = System.currentTimeMillis();
}
其中使用到了heavyIOExecutor()方法,实际上是创建一个核心线程池数为10的ScheduledThreadPoolExecutor定时任务线程池。
这里需要注意,每一个key都对应着线程池中的一个线程,由于ScheduledThreadPoolExecutor使用的是DelayedWorkQueue无界队列,因此@RefreshCache注解的滥用容易引发问题,需要注意这个注解的主要用处是用来防止缓存雪崩,而不应该在有多种参数的方法上随意的使用这个注解,可能会导致每一个jvm的线程池中都有大量的线程,占用内存过多导致发生问题。
public static ScheduledExecutorService heavyIOExecutor() {
if (heavyIOExecutor != null) {
return heavyIOExecutor;
}
synchronized (JetCacheExecutor.class) {
if (heavyIOExecutor == null) {
ThreadFactory tf = r -> {
Thread t = new Thread(r, "JetCacheHeavyIOExecutor" + threadCount++);
t.setDaemon(true);
return t;
};
heavyIOExecutor = new ScheduledThreadPoolExecutor(
10, tf, new ThreadPoolExecutor.DiscardPolicy());
}
}
return heavyIOExecutor;
}
二、不严格的分布式锁
- 这里分布式锁是在Cache中定义的,也就是说如果是由本地Cache来调用,那么就是一个本地锁。如果是远程cache来调用,实现的就是一个不严格的分布式锁。
- 可以看到下面代码,调用了PUT_IF_ABSENT方法,在RedisCache中的实现是用了Redis的setnx命令,存入key,然后将uuid作为value,实现加锁。
- 流程大致是首先调用PUT_IF_ABSENT方法,如果成功,可获得锁,如果失败则进入inquiry阶段。
- inquiry阶段会调用GET方法,如果获取到值,会判断uuid是否相同,相同说明是自己的锁,成功获取,否则获取锁失败。
- tryLock方法会返回一个AutoReleaseLock对象,用来解锁。具体实现是调用REMOVE方法,把key从远程cache中删除掉,实现解锁。
default AutoReleaseLock tryLock(K key, long expire, TimeUnit timeUnit) {
if (key == null) {
return null;
}
final String uuid = UUID.randomUUID().toString();
final long expireTimestamp = System.currentTimeMillis() + timeUnit.toMillis(expire);
final CacheConfig config = config();
AutoReleaseLock lock = () -> {
int unlockCount = 0;
while (unlockCount++ < config.getTryLockUnlockCount()) {
if(System.currentTimeMillis() < expireTimestamp) {
CacheResult unlockResult = REMOVE(key);
if (unlockResult.getResultCode() == CacheResultCode.FAIL
|| unlockResult.getResultCode() == CacheResultCode.PART_SUCCESS) {
logger.info("[tryLock] [{} of {}] [{}] unlock failed. Key={}, msg = {}",
unlockCount, config.getTryLockUnlockCount(), uuid, key, unlockResult.getMessage());
// retry
} else if (unlockResult.isSuccess()) {
logger.debug("[tryLock] [{} of {}] [{}] successfully release the lock. Key={}",
unlockCount, config.getTryLockUnlockCount(), uuid, key);
return;
} else {
logger.warn("[tryLock] [{} of {}] [{}] unexpected unlock result: Key={}, result={}",
unlockCount, config.getTryLockUnlockCount(), uuid, key, unlockResult.getResultCode());
return;
}
} else {
logger.info("[tryLock] [{} of {}] [{}] lock already expired: Key={}",
unlockCount, config.getTryLockUnlockCount(), uuid, key);
return;
}
}
};
int lockCount = 0;
Cache cache = this;
while (lockCount++ < config.getTryLockLockCount()) {
CacheResult lockResult = cache.PUT_IF_ABSENT(key, uuid, expire, timeUnit);
if (lockResult.isSuccess()) {
logger.debug("[tryLock] [{} of {}] [{}] successfully get a lock. Key={}",
lockCount, config.getTryLockLockCount(), uuid, key);
return lock;
} else if (lockResult.getResultCode() == CacheResultCode.FAIL || lockResult.getResultCode() == CacheResultCode.PART_SUCCESS) {
logger.info("[tryLock] [{} of {}] [{}] cache access failed during get lock, will inquiry {} times. Key={}, msg={}",
lockCount, config.getTryLockLockCount(), uuid,
config.getTryLockInquiryCount(), key, lockResult.getMessage());
int inquiryCount = 0;
while (inquiryCount++ < config.getTryLockInquiryCount()) {
CacheGetResult inquiryResult = cache.GET(key);
if (inquiryResult.isSuccess()) {
if (uuid.equals(inquiryResult.getValue())) {
logger.debug("[tryLock] [{} of {}] [{}] successfully get a lock after inquiry. Key={}",
inquiryCount, config.getTryLockInquiryCount(), uuid, key);
return lock;
} else {
logger.debug("[tryLock] [{} of {}] [{}] not the owner of the lock, return null. Key={}",
inquiryCount, config.getTryLockInquiryCount(), uuid, key);
return null;
}
} else {
logger.info("[tryLock] [{} of {}] [{}] inquiry failed. Key={}, msg={}",
inquiryCount, config.getTryLockInquiryCount(), uuid, key, inquiryResult.getMessage());
// retry inquiry
}
}
} else {
// others holds the lock
logger.debug("[tryLock] [{} of {}] [{}] others holds the lock, return null. Key={}",
lockCount, config.getTryLockLockCount(), uuid, key);
return null;
}
}
logger.debug("[tryLock] [{}] return null after {} attempts. Key={}", uuid, config.getTryLockLockCount(), key);
return null;
}