背景

项目组已经有个

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

思路

思路参考了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

redis续期_redis分布式锁自动延长过期时间相关推荐

  1. java redis set 过期时间_redis分布式锁自动延长过期时间

    分布式系统概念与设计(原书第5版) 93.8元 包邮 (需用券) 去购买 > 背景项目组已经有个分布式锁注解(参考前文<记一次分布式锁注解化>),但是在设置锁过期时间时,需要去预估业 ...

  2. redis set 超时_redis分布式锁3种实现方式对比分析总结

    我在这篇文章提到了分布式锁,但没有展开来讲,抛砖引玉,今天就来说说高并发服务编程中的redis分布式锁. 这里罗列出3种redis实现的分布式锁,并分别对比说明各自特点. Redis单实例分布式锁 实 ...

  3. redis ,redisson 分布式锁深入剖析

    目录 为什么要用分布式锁? 分布式锁所遵循的原则? redis 分布式锁 redis 原始分布式锁实现 加锁 释放锁 redis 分布式锁存在的问题 redisson  实现分布式锁 redisson ...

  4. Redis:Redisson分布式锁的使用(推荐使用)

    Redis:Redisson分布式锁的使用(生产环境下)(推荐使用) 关键词 基于NIO的Netty框架,生产环境使用分布式锁 redisson加锁:lua脚本加锁(其他客户端自旋) 自动延时机制:启 ...

  5. 关于分布式锁的续命问题——基于Redis实现的分布式锁

    目录 一.背景 二.自定义实现 三.Redisson框架实现 一.背景 关于分布式锁就不多说了,现在出现了一种场景,如果在分布式锁中,业务代码没有执行完,然后锁的键值过期了,那么其他的JVM就可能会获 ...

  6. 17、Redis、Zk分布式锁实现原理

    我们在编程有很多场景使用本地锁和分布式锁,但是是否考虑这些锁的原理是什么?本篇讨论下实现分布式锁的常见办法及他们实现原理. 一.使用锁的原则 使用本地锁和分布式锁是为了解决并发导致脏数据的场景,使用锁 ...

  7. redis和redission分布式锁原理及区别

    redis和redission分布式锁原理及区别 我最近做租车项目,在处理分布式时用到分布式锁,我发现很多同事都在网上找分布式锁的资料,但是看的资料都不是很全,所以在这里我谈谈自己的分布式锁理解. 结 ...

  8. Redlock——Redis集群分布式锁

    欢迎关注方志朋的博客,回复"666"获面试宝典 前言 分布式锁是一种非常有用的技术手段.实现高效的分布式锁有三个属性需要考虑: 安全属性:互斥,不管什么时候,只有一个客户端持有锁 ...

  9. 阿里JAVA面试题剖析:一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?...

    面试原题 一般实现分布式锁都有哪些方式?使用 redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? 面试官心理分析 其实一般问问题,都是这么问的,先 ...

最新文章

  1. 【转】MySQL常用命令总结
  2. CentOS 7.3 源码安装 OpenVAS 9
  3. Java多线程详解(二)
  4. Linux常用命令----文件处理命令
  5. STL之set集合容器
  6. 使用LoadRunner对Web Services进行调用--Add Service Call
  7. USTC English Club Note20171023
  8. socket通信和异常处理札记
  9. python字符串结合操作符的使用
  10. C语言printf 和 scanf 用法
  11. Java 算法 换零钞
  12. 《从零开始走进FPGA》导读
  13. Jquery图片放大镜效果
  14. 在线绘图(PS)(海报)
  15. fp16和fp32,神经网络混合精度训练,PYTORCH 采用FP16,Libtorch采用FP16,神经网络混合精度三种避免损失,TensorRT模型转换及部署(一)
  16. 哨兵二号数据下载的手把手指导以及12.5米DEM数据下载
  17. python中撤销的快捷键_python常用快捷键
  18. python导入wx_Python“导入wx”
  19. AD18如何制作logo
  20. 站内信(系统消息) 发送给所有用户

热门文章

  1. 曾经的Python爬虫挣钱生活
  2. Numpy之国际象棋棋盘(8行8列)
  3. Java 详解数字格式化(NumberFormatDecimalFormat)
  4. 操作系统概念笔记——第六章:进程同步
  5. 办公自动化--python-pptx
  6. 为什么越来越多的人选择智能语音外呼机器人项目创业?
  7. elementui table 第一列内容相同 自动合并单元格 el-table第一列内容相同自动合并
  8. 《高效的秘密》第三,四章读后感
  9. 小程序对接停车场支付流程思考
  10. 求自然数的平方和python_查找最大N,以使Python中前N个自然数的平方和不超过X