redis续期_redis分布式锁自动延长过期时间

背景

项目组已经有个

分布式锁注解(参考前文《记一次分布式锁注解化》),但是在设置锁过期时间时,需要去预估业务耗时时间,如果锁的过期时间能根据业务运行时间自动调整,那使用的就更方便了。

思路

思路参考了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 keys = Lists.newArrayList(lockKey); List 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 keys = Lists.newArrayList(lock.getLockKey()); List 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;

}

文章来源: segmentfault.com,作者:noname,版权归原作者所有,如需转载,请联系作者。

原文链接:segmentfault.com/a/1190000037526623


版权声明:本文为weixin_42319436原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。