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

27e3785d6f233b9d670a807e8d557852.png

点击上方 " JudyGirl "关注, 星标或置顶一起成长 作者 : noname 来源: https://segmentfault.com/a/1190000037526623?utm_source=tuicool&utm_medium=referral

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

思路 思路参考了 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;
}
热门内容:
  • 记一次内存飙升的Windbg

  • 分布式架构数据存储设计与实践
  • 服务容错场景分析实现方案

f7f3683c68f4f6fcf5c9b4f803a99be3.png                                           

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值