背景

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

思路

思路参考了redisson

  1. 保留原先的可自定义设置过期工夫,只有在没有设置过期工夫(过期工夫为默认值0)的状况下,才会启动主动缩短。
  2. 申请锁时,设置一个缩短过期工夫,定时每隔缩短过期工夫的三分之一工夫就从新设置过期工夫期间工夫值为缩短过期工夫)。
  3. 为了避免某次业务因为异样而呈现工作继续很久,从而长时间占有了锁,增加最大延期次数参数。

加锁

  1. 用一个Map来存储须要续期的工作信息
  2. 在加锁胜利之后将工作信息放入Map,并启动提早工作,提早工作在执行延期动作前先查看下Map里锁数据是不是还是被当前任务持有。
  3. 每次续期工作实现并且胜利之后,就再次启动提早工作。
申请锁

复用之前的加锁办法,把缩短过期工夫作为加锁过期工夫

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;}