背景
项目组曾经有个
分布式锁
注解(参考前文《记一次分布式锁注解化》),然而在设置锁过期工夫时,须要去预估业务耗时工夫,如果锁的过期工夫能依据业务运行工夫主动调整,那应用的就更不便了。
思路
思路参考了redisson
:
- 保留原先的可自定义设置过期工夫,只有在
没有设置过期工夫(过期工夫为默认值 0)
的状况下,才会启动主动缩短。 - 申请锁时,设置一个
缩短过期工夫
,定时每隔缩短过期工夫
的三分之一工夫就从新设置过期工夫
(期间工夫
值为缩短过期工夫
)。 - 为了避免某次业务因为异样而呈现
工作继续很久
,从而长时间占有了锁,增加最大延期次数
参数。
加锁
- 用一个
Map
来存储须要续期的工作信息
。 - 在加锁胜利之后将
工作信息
放入Map
,并启动提早工作,提早工作在执行延期动作
前先查看下Map
里锁数据是不是还是被当前任务持有。 - 每次续期工作实现并且胜利之后,就再次启动提早工作。
申请锁
复用之前的 加锁
办法,把 缩短过期工夫
作为 加锁过期工夫
。
public Lock acquireAndRenew(String lockKey, String lockValue, int lockWatchdogTimeout) {return acquireAndRenew(lockKey, lockValue, lockWatchdogTimeout, 0);
}
public Lock acquireAndRenew(String lockKey, String lockValue, int lockWatchdogTimeout, int maxRenewTimes) {if (lockKey == null || lockValue == null || lockWatchdogTimeout <= 0) {return new Lock(this).setSuccess(false).setMessage("illegal argument!");
}
Lock lock = acquire(lockKey, lockValue, lockWatchdogTimeout);
if (!lock.isSuccess()) {return lock;}
expirationRenewalMap.put(lockKey, new RenewLockInfo(lock));
scheduleExpirationRenewal(lockKey, lockValue, lockWatchdogTimeout, maxRenewTimes, new AtomicInteger());
return lock;
}
定时续期
以后锁还未被开释(Map 里还有数据
),并且以后 延期
工作执行胜利,则持续下一次工作。
private void scheduleExpirationRenewal(String lockKey, String lockValue, int lockWatchdogTimeout,
int maxRenewTimes, AtomicInteger renewTimes) {ScheduledFuture<?> scheduledFuture = scheduledExecutorService.schedule(() -> {
try {if (!renewExpiration(lockKey, lockValue, lockWatchdogTimeout)) {log.debug("dislock renew[{}:{}] fail!", lockKey, lockValue);
return;
}
if (maxRenewTimes > 0 && renewTimes.incrementAndGet() == maxRenewTimes) {log.info("dislock renew[{}:{}] override times[{}]!", lockKey, lockValue, maxRenewTimes);
return;
}
scheduleExpirationRenewal(lockKey, lockValue, lockWatchdogTimeout, maxRenewTimes, renewTimes);
} catch (Exception e) {log.error("dislock renew[{}:{}] error!", lockKey, lockValue, e);
}
}, lockWatchdogTimeout / 3, TimeUnit.MILLISECONDS);
RenewLockInfo lockInfo = expirationRenewalMap.get(lockKey);
if (lockInfo == null) {return;}
lockInfo.setRenewScheduledFuture(scheduledFuture);
}
private boolean renewExpiration(String lockKey, String lockValue, int lockWatchdogTimeout) {RenewLockInfo lockInfo = expirationRenewalMap.get(lockKey);
if (lockInfo == null) {return false;}
if (!lockInfo.getLock().getLockValue().equals(lockValue)) {return false;}
List<String> keys = Lists.newArrayList(lockKey);
List<String> args = Lists.newArrayList(lockValue, String.valueOf(lockWatchdogTimeout));
return (long) jedisTemplate.evalsha(renewScriptSha, keys, args) > 0;
}
延期脚本
public void init() {
……
String renewScript = "if redis.call('get',KEYS[1]) == ARGV[1] then \n" +
"redis.call('pexpire', KEYS[1], ARGV[2]) \n" +
"return 1 \n" +
"end \n" +
"return 0";
renewScriptSha = jedisTemplate.scriptLoad(renewScript);
}
开释
执行 开释
之前,先将数据从 Map
里革除掉。
public boolean release(Lock lock) {if (!ifReleaseLock(lock)) {return false;}
// 放在 redis 脚本后面,避免 redis 删除失败,而 map 没有清理,从而导致 redis 无限期续期
try {RenewLockInfo lockInfo = expirationRenewalMap.get(lock.getLockKey());
if (lockInfo != null) {ScheduledFuture<?> scheduledFuture = lockInfo.getRenewScheduledFuture();
if (scheduledFuture != null) {scheduledFuture.cancel(false);
}
}
} catch (Exception e) {log.error("dislock cancel renew scheduled[{}:{}] error!", lock.getLockKey(), lock.getLockValue(), e);
}
expirationRenewalMap.remove(lock.getLockKey());
List<String> keys = Lists.newArrayList(lock.getLockKey());
List<String> args = Lists.newArrayList(lock.getLockValue());
return (long) jedisTemplate.evalsha(releaseScriptSha, keys, args) > 0;
}
注解革新
注解类
注解减少两个参数,并且原先的过期工夫参数默认值改为 0
,即默认启动 主动延期
。
@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface DisLock {
int DEFAULT_EXPIRE = -1;
int DEFAULT_LOCK_WATCHDOG_TIMEOUT = 30000;
…… // 其余参数
/**
* 默认 key 过期工夫,单位毫秒
*
* @return long
* @author
* @date 2020-03-17 22:50
*/
int expire() default DEFAULT_EXPIRE;
/**
* 监控锁的看门狗超时工夫,单位毫秒,参数用于主动续约过期工夫
* 参数只实用于分布式锁的加锁申请中未明确应用 expire 参数的状况(expire 等于默认值 DEFAULT_EXPIRE)。*
* @return int
* @author
* @date 2020-10-14 11:08
*/
int lockWatchdogTimeout() default DEFAULT_LOCK_WATCHDOG_TIMEOUT;
/**
* 最大续期次数,用于避免业务过程迟缓在导致长时间占有锁
*
* @return int 大于 0 时无效,小于等于 0 示意无限度
* @author
* @date 2020-10-15 16:23
*/
int maxRenewTimes() default 0;}
注解解决类
JedisDistributedLock.Lock lock = jedisDistributedLock.acquire(key, value, disLock.expire());
改成
JedisDistributedLock.Lock lock;
if (ifRenew(disLock)) {
lock = jedisDistributedLock
.acquireAndRenew(key, value, disLock.lockWatchdogTimeout(), disLock.maxRenewTimes());
} else {lock = jedisDistributedLock.acquire(key, value, disLock.expire());
}
protected boolean ifRenew(DisLock disLock) {return disLock.expire() == DisLock.DEFAULT_EXPIRE;
}