背景
项目组曾经有个分布式锁
注解(参考前文《记一次分布式锁注解化》),然而在设置锁过期工夫时,须要去预估业务耗时工夫,如果锁的过期工夫能依据业务运行工夫主动调整,那应用的就更不便了。
思路
思路参考了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;}