当前位置: 首页 > news >正文

详解Redis锁误删、原子性难题及Redisson加锁底层原理、WatchDog续约机制

在这里插入图片描述

👨‍💻程序员三明治:个人主页

🔥 个人专栏: 《设计模式精解》 《重学数据结构》

🤞先做到 再看见!

> set lock:swz threadId ex 5 nx ok
.....do something
> del lock:swz

大家肯定都用过上面的这种set分布式锁的命令,那这种命令会有什么问题呢?

毫无疑问,误删的问题。也就是我线程A的业务逻辑还没执行完却到了超时时间导致锁释放,释放之后线程B拿到了锁,然后此时线程A的业务逻辑执行完以后要del释放锁,就会把线程B的锁删掉。

解决方式:

这里static会使同一个jvm的所有线程的uuid一样,但是线程id本身是不一样的,而两个jvm的线程id有可能相同,但是uuid不一样

判断锁标识和释放锁是两个操作,如何保证原子性?

因为判断完锁标识之后在释放锁之前有可能遇到full gc导致阻塞,如果阻塞时间超过了超时时间,所就被自动释放。等我不阻塞了再去del锁就可能会把其他线程的锁删掉。

lua脚本可以解决:

Redisson是如何实现分布式锁的?

Redisson通过Lua脚本执行加锁操作。Lua脚本会判断锁是否存在,如果不存在,使用 hset 命令设置锁的值,使用 pexpire 命令设置锁的过期时间。如果锁存在而且当前线程持有,则将锁的值加1,并设置锁的过期时间为指定的时间。

两个图分别是获取锁和释放锁的流程

Redisson加锁的源码

释放锁的源码


解决问题一:不可重入
set命令的分布式锁不可以实现锁重入,因为同一个线程setnx都是一样的
故可以参考ReentrantLock实现锁重入的原理,每次相同的线程又一次来获取锁就对state变量值进行++操作
分布式锁Redisson和自定义分布式锁的一些区别:底层是hash结构,因为value部分不仅仅要存储"获取到锁的线程标识",还要存储"当前线程重入的次数"(支持锁重入的原理).
解决问题二:不可重试
set命令的分布式锁获取锁失败之后会立即返回false,不会重试。
源码上tryLock()API中传入了最大等待时间,则说明开启了失败重试机制
通过源码分析,当第一次获取锁失败后且还处于等待时间时,不会立马再去尝试获取锁,而是先去订阅,订阅别人释放锁的信号,保证当有人执行完释放锁的lua脚本后它能监听到。但是一旦等待的时间超过了最大等待时间waitTime,就会取消订阅,不会重试了。
订阅之后会在while(true)的一个死循环中不断的等待 尝试 等待 尝试…
在释放锁的源码lua中,释放锁最后会发布一个释放锁的信号,这些订阅了的线程就会监听到!!!
解决问题三:超时释放(看门狗机制)
只有获取锁成功了才会有leasetime的事情

  1. 获取锁成功且当leaseTime为-1时(默认)[-1才会走看门狗的逻辑]:
    如果获取锁成功,会有一个自动更新过期时间的一个函数.
    Redisson有一个自动续约更新的静态变量map,专门用于存储Redisson的不同锁的实例,就是Redisson创建的各个锁的实例都会被放到Redisson是静态变量map中,以锁的名字为key,每个锁创建的对应的Entry为value存入。达到的效果就是只要是同一个锁实例,不管来几次,将来拿到的永远是同一个entry。每一个锁实例都有自己对应的一个entry,entry中存放了threadId和定时任务,这个定时任务就是每隔10s会递归的调用自己,在单层递归的逻辑中完成对于锁过期时间的刷新…保证锁永不过期。直到当这把锁被unlock()成功之后,会从map中剔除锁实例,并清除锁对应的entry中存储的threadId和定时任务…
    照这样锁什么时候释放???在unlock锁成功释放之后,有一个回调函数,会取消这个定时的更新任务.
  2. 获取锁成功且当leasetime不为-1时(自己传值)

如果获取锁成功后,leasetime传入的不是-1,而是一个具体的时间,比如20s,那么Redisson会在20s后自动释放该锁,即使业务阻塞时间超过20s,锁也会被释放。在这种情况下,Redisson并不会启动开门狗机制,因为它知道锁的过期时间是20s,所以不需要再去刷新锁的过期时间。如果你希望锁一直有效,可以将leasetime设置为-1,这样Redisson会开启开门狗机制,确保锁一直有效,直到被释放。

// 看门狗续约核心源码
private void renewExpiration() {// 从续约Map中获取当前锁的条目ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee == null) {return;  // 条目不存在,说明锁已释放}// 创建定时任务,在租期的1/3时间后执行续约Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {// 🎯 获取当前锁的续约条目ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ent == null) {return;  // 锁已释放,停止续约}// 🎯 获取第一个线程ID(支持可重入)Long threadId = ent.getFirstThreadId();if (threadId == null) {return;  // 没有线程持有锁,停止续约}// 🎯 执行异步续约操作RFuture<Boolean> future = renewExpirationAsync(threadId);future.onComplete((res, e) -> {if (e != null) {// 续约异常,记录日志并停止log.error("Can't update lock " + getName() + " expiration", e);return;}if (res) {// 🎯 续约成功,递归调用继续下一次续约renewExpiration();} else {// 🎯 续约失败,取消续约任务cancelExpirationRenewal(threadId);}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);  // 🎯 默认10秒后执行ee.setTimeout(task);  // 保存定时任务引用
}

锁释放时清理看门狗

void cancelExpirationRenewal(Long threadId) {ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (task == null) {return;}if (threadId != null) {task.removeThreadId(threadId);}if (threadId == null || task.hasNoThreads()) {// 取消定时任务task.getTimeout().cancel();EXPIRATION_RENEWAL_MAP.remove(getEntryName());}
}
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {return get(tryAcquireAsync(leaseTime, unit, threadId));
}private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {if (leaseTime != -1) {return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);}// 如果未指定leaseTime,使用看门狗机制RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);ttlRemainingFuture.onComplete((ttlRemaining, e) -> {if (e != null) {return;}// 锁获取成功,启动看门狗续期if (ttlRemaining == null) {scheduleExpirationRenewal(threadId);}});return ttlRemainingFuture;
}

Redisson分布式锁原理图






如果我的内容对你有帮助,请辛苦动动您的手指为我点赞,评论,收藏。感谢大家!!
在这里插入图片描述

http://www.dtcms.com/a/498668.html

相关文章:

  • 【Java 集合】核心知识点梳理
  • 郑州建设厅官方网站地方网站推广
  • 宁波网站建设建站厂家wordpress 站点描述
  • 兴扬汽车网站谁做的公司设计图
  • 上海石化有做网站设计的吗广西网络广播电视台直播
  • 网站和推广在一家做的好处卓智网络科技有限公司
  • 推广网站有哪些做网站销售水果
  • 产品网站推广淄博做网站建设的公司
  • 整站下载器 安卓版企业网站多大空间够用
  • 博罗做网站战队头像在线制作免费
  • 东莞专业网站建设推广欧洲c2c平台
  • 手机网站做指向沃尔玛网上商城可以用购物卡吗
  • 中国建设银行北京市互联网网站成都小程序开发公司找哪家
  • 电商网站要素如何提升网站营销力
  • 成都模板网站建设服务深圳创纪录暴雨19小时
  • 网站地图后缀响应式博客网站模板
  • 嘉兴企业网站模板聚美优品
  • 展示网站模版源码wordpress 2栏主题
  • 网站建设与排名网站广告位代码
  • 营销型企业网站策划方案村网站开设两学一做栏目
  • 靓号网建站百度推广手机app下载
  • WordPress网站打不开nginx关wordpress更新
  • 四川省住房与城乡建设厅网站官网wordpress 中文版 英文版
  • aspx网站模板手机模块网站
  • 单位网站建设程序网站建设与推广的实训报告
  • 什么网站可以做投资做网站需要关注哪些
  • 做网站补贴网页广告拦截
  • 网站优化关键词怎么做贵阳搜索引擎排名推广
  • 网站设计制作托管维护网站备案公告
  • 网站以前在百度能搜索不到公司变更法人流程