Redisson分布式锁源码解析、集群环境存在的问题

一、使用Redisson步骤

Redisson各个锁基本所用Redisson各个锁基本所用Redisson各个锁基本所用

二、源码解析

lock锁

1) 基本思想:

lock有两种方法 一种是空参  另一种是带参
         * 空参方法:会默认调用看门狗的过期时间30*1000(30秒)
         * 然后在正常运行的时候,会启用定时任务调用重置时间的方法(间隔为开门看配置的默认过期时间的三分之一,也就是10秒)
         * 当出现错误的时候就会停止续期,直到到期释放锁或手动释放锁
         * 带参方法:手动设置解锁时间,到期后自动解锁,或者业务完成后手动解锁,不会自动续期

源码:

Lock

调用lockInterruptibly()方法会默认传入lease 为-1,该值再后面起作用

  public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {long threadId = Thread.currentThread().getId();//获取该锁的过期时间,如果该锁没被持有,会返回一个null,如果被持有 会返回一个过期时间Long ttl = this.tryAcquire(leaseTime, unit, threadId);if (ttl != null) {//ttl不为null,说明锁已经被抢占了RFuture<RedissonLockEntry> future = this.subscribe(threadId);this.commandExecutor.syncSubscription(future);try {//开始循环获取锁while(true) {//刚进如循环先尝试获取锁,获取成功返回null,跳出循环,获取失败,则继续往下走ttl = this.tryAcquire(leaseTime, unit, threadId);if (ttl == null) {return;}if (ttl >= 0L) {//如果过期时间大于0,则调用getLatch// 返回一个信号量,开始进入阻塞,阻塞时长为上一次锁的剩余过期时长,并且让出cup//有阻塞必然有唤醒,位于解锁操作中this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} else {this.getEntry(threadId).getLatch().acquire();}}} finally {this.unsubscribe(future, threadId);}}}

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {//如果leaseTime != -1,即不等于默认值,则表示手动设置了过期时间if (leaseTime != -1L) {return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {//如果leaseTime = -1,表示使用默认方式,即使用看门狗默认实现自动续期RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);ttlRemainingFuture.addListener(new FutureListener<Long>() {public void operationComplete(Future<Long> future) throws Exception {//如果tryLockInnerAsync执行成功if (future.isSuccess()) {//获取过期时间Long ttlRemaining = (Long)future.getNow();//过期时间为空,表示加锁成功if (ttlRemaining == null) {//开启刷新重置过期时间步骤RedissonLock.this.scheduleExpirationRenewal(threadId);}}}});return ttlRemainingFuture;}}

//  lua脚本尝试抢占锁,失败返回锁过期时间<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {this.internalLockLeaseTime = unit.toMillis(leaseTime);//直接使用lua脚本发起命令//通过lua脚本可以看出,redisson加锁除了使用自定义的名字以外,还要使用uuid// 加上当前线程的threadId组合,以自定义名字作hash的key,使用return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command,//如果该锁未被占有,则设置锁,设置过期时间,过期时间为 internalLockLeaseTime ,然后返回null"if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]);return nil; end; " +//如果锁已经被占有,判断是否是重入锁,如果是重入锁,则将value增加1 ,代表重入,并且设置过期时间,返回null。"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; " +//如果已经被站有所,且不是重入锁,则返回过期时间"return redis.call('pttl', KEYS[1]);",Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});}

看门狗续命

//看门狗续命机制private void scheduleExpirationRenewal(final long threadId) {//首先会判断该线程是否已经再重置时间的map中,仅仅第一次进来是空的。if (!expirationRenewalMap.containsKey(this.getEntryName())) {//使用了看门狗默认的时间(30秒) 除以3 ,也就是延迟10秒后执行Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {public void run(Timeout timeout) throws Exception {//判断是否该线程是否还持有锁,如果持有,返回1,并且设置过期时间,如果没持有,返回0RFuture<Boolean> future = RedissonLock.this.commandExecutor.evalWriteAsync(RedissonLock.this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end; return 0;",Collections.singletonList(RedissonLock.this.getName()), new Object[]{RedissonLock.this.internalLockLeaseTime, RedissonLock.this.getLockName(threadId)});future.addListener(new FutureListener<Boolean>() {public void operationComplete(Future<Boolean> future) throws Exception {//从map中移除该线程,这样下次再调用该方法仍然可以执行RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName());if (!future.isSuccess()) {RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", future.cause());} else {if ((Boolean)future.getNow()) {//当lua脚本返回1表是true,也就是仍然持有锁,则递归调用该方法,RedissonLock.this.scheduleExpirationRenewal(threadId);}}}});}}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);if (expirationRenewalMap.putIfAbsent(this.getEntryName(), task) != null) {task.cancel();}}}

2、unlock

源码

    public RFuture<Void> unlockAsync(final long threadId) {final RPromise<Void> result = new RedissonPromise();//调用lua脚本释放锁RFuture<Boolean> future = this.unlockInnerAsync(threadId);future.addListener(new FutureListener<Boolean>() {public void operationComplete(Future<Boolean> future) throws Exception {if (!future.isSuccess()) {result.tryFailure(future.cause());} else {Boolean opStatus = (Boolean)future.getNow();//如果锁状态为null,表示存在异常,为正常释放锁之前,被别人占领锁了if (opStatus == null) {IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + RedissonLock.this.id + " thread-id: " + threadId);result.tryFailure(cause);} else {//如果返回0.为false 表示可重入锁,不取消重置过期时间,//返回1 为true,表示已解锁,取消重置过期时间if (opStatus) {RedissonLock.this.cancelExpirationRenewal();}//解锁result.trySuccess((Object)null);}}}});return result;}

protected RFuture<Boolean> unlockInnerAsync(long threadId) {return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,//当key不存在,表示锁未被持有,说明不用解锁了,返回1 ,1在后续表示取消重置过期时间"if (redis.call('exists', KEYS[1]) == 0) then redis.call('publish', KEYS[2], ARGV[1]); return 1; end;" +//key存在,但是持有锁的线程不是当前线程,返回null,后面会提出一个异常"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil;end; " +//锁状态-1后仍然大于0,表示可重入锁,仍处于锁定状态,返回0,0在后续表示 不做处理,仍然重置过期时间"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); return 0; " +//返回锁状态不大于0,正常解锁,返回1,1在后续表示取消重置过期时间"else redis.call('del', KEYS[1]); redis.call('publish', KEYS[2], ARGV[1]); return 1; end; " +"return nil;", Arrays.asList(this.getName(), this.getChannelName()), new Object[]{LockPubSub.unlockMessage, this.internalLockLeaseTime, this.getLockName(threadId)});}

三、集群环境下潜在问题

在Redis主从架构+哨兵模式的环境下,业务系统已经成功获取了锁,redis写入数据,但是正要往从库上存数据时,发生主库宕机的情况,从库在哨兵的选举下成为了主库,而另外一个业务请求再次需要获取锁,会直接访问到新的主库,而此时新主库是没有锁信息的,此时就会出现业务重复的情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/165538.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

内网穿透的应用-如何在本地安装Flask,以及将其web界面发布到公网上并进行远程访问

轻量级web开发框架&#xff1a;Flask本地部署及实现公网访问界面 文章目录 轻量级web开发框架&#xff1a;Flask本地部署及实现公网访问界面前言1. 安装部署Flask2. 安装Cpolar内网穿透3. 配置Flask的web界面公网访问地址4. 公网远程访问Flask的web界面 前言 本篇文章讲解如何…

linux环境下samba服务器的配置

linux服务器怎么创建用户 在Linux服务器上&#xff0c;可以使用以下步骤创建用户&#xff1a; 使用adduser命令创建新用户&#xff1a; sudo adduser username将 username 替换为你要创建的用户名。这个命令会提示你输入新用户的密码以及其他相关信息。 如果需要为新用户设…

qml PathPercent使用介绍

PathPercent 是一个QML类型,它表示 Path 上的一个百分比位置。这个类型通常在 PathAnimation 或 PathInterpolator 中使用,以便在路径上产生一个特定的位置。它提供了一种方式来表示在 Path 元素上的某个点。通过 PathPercent,你可以指定一个百分比,来表示沿着路径的位置,…

『亚马逊云科技产品测评』活动征文|通过Lightsail搭建个人笔记

提示&#xff1a;授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 文章目录 前言实践知识储备Lightsail介绍Leanote介绍实践…

系统架构设计: 21 论敏捷软件开发方法及其应用

论敏捷软件开发方法及其应用 请围绕“敏捷软件开发方法及其应用”论题,依次从以下三个方面进行论述。 ①简述你所参与开发的运用了敏捷技术的项目,以及你所担任的工作; ②分析并讨论敏捷<

VSCode插件koroFileHeader的使用。

文章目录 前言一、koroFileHeader是什么&#xff1f;二、使用步骤1.安装1.配置2.食用 前言 今天的天气还不错&#xff0c;真是金风玉露一相逢&#xff0c;便胜却人间无数&#xff0c;写篇博客玩玩&#xff0c;主题&#xff1a;注释。注释的本质就是对代码的解释和说明&#xf…

nginx 配置静态缓存全教程 (以及静态缓存文件没有生成)

一、第一步定义一个缓存目录设置目录结构 在 http 模块下定义(keys_zone 缓存区名&#xff1a;后面是缓存区大小 inactive 不活跃的文件多久清理 max_size 缓存区所占磁盘的上限 use_temp_path 默认关闭&#xff08;有需要自己百度&#xff09;) proxy_cache_path /path/your…

16 Go的反射

概述 在上一节的内容中&#xff0c;我们介绍了Go的并发&#xff0c;包括&#xff1a;Goroutines、Channels、WaitGroups、Mutex、Select等。在本节中&#xff0c;我们将介绍Go的反射。Go语言中的反射是一种在运行时检查类型信息并操作对象的能力&#xff0c;通过反射&#xff0…

STM32_6(TIM)

TIM定时器&#xff08;第一部分&#xff09; TIM&#xff08;Timer&#xff09;定时器定时器可以对输入的时钟进行计数&#xff0c;并在计数值达到设定值时触发中断16位计数器、预分频器、自动重装寄存器的时基单元&#xff0c;在72MHz计数时钟下可以实现最大59.65s的定时不仅…

在游戏开发中,实时渲染和离线渲染对于游戏平衡的影响有哪些?

实时渲染和离线渲染对游戏平衡有那些影响呢&#xff1f;在游戏开发中&#xff0c;渲染方式的选择对游戏的整体表现和玩家体验有着至关重要的作用。那么&#xff0c;实时渲染和离线渲染究竟有哪些利弊呢&#xff1f; 一、实时渲染 实时渲染&#xff0c;顾名思义&#xff0c;是…

Ubuntu 1.84.2Visual Studio Code 下载配置与vscode查看内存Hex Editor插件,简单易懂

目录 前言 一 首先我为啥要重装Vs Code呢&#xff1f; 二 下载1.84.2Visual Studio Code 三 配置Vscode终端字体 四 安装插件 前言 这是一篇将老版本的VsCode下载至最新版的博文&#xff0c;从下载到调试全篇 一 首先我为啥要重装Vs Code呢&#xff1f; 因为我想安装这个…

网络安全深入学习第九课——本机信息收集

文章目录 一、Windows基本信息收集1、查看当前权限2、查看指定用户的详细信息3、查看用户SID4、查看网卡配置5、查看服务器版本\补丁等6、查看系统架构7、查看安装的软件及版本8、查看本机服务信息9、查询进程信息和列表10、查看启动程序信息11、查看计划任务12、查看主机开机时…

1、postman的安装及使用

一、安装、登录 1.安装 下载地址 2.注册登录&#xff08;保存云服务进度&#xff09; 二、界面介绍 三、执行接口测试页面 请求页签&#xff1a; 1、params&#xff1a;当是get请求时&#xff0c;通过params传参 2、authorization&#xff1a;鉴权 3、headers&#xff1…

大数据-之LibrA数据库系统告警处理(ALM-37000 MPPDBServer数据目录或Redo目录缺失)

告警解释 当出现如下情况时&#xff0c;产生该告警&#xff1a; 数据实例数据目录被删除。数据实例Redo目录&#xff08;pg_xlog&#xff09;被删除。 告警属性 告警ID 告警级别 可自动清除 37000 严重 是 告警参数 参数名称 参数含义 ServiceName 产生告警的服务…

OTP语音芯片WTN6系列:多样化选择,满足各种产品应用需求

随着科技的快速发展&#xff0c;语音芯片已经成为了智能产品中不可或缺的核心组件。在这个领域中&#xff0c;唯创知音OTP语音芯片WTN6系列以其出色的性能和多样化的选择&#xff0c;赢得了广大开发者的青睐。本文将详细介绍WTN6系列的几个重要型号及其特点&#xff0c;并为读者…

idea 26 个天花板技巧

1、 查看代码历史版本&#xff1b;2、 调整idea的虚拟内存&#xff1a;&#xff1b;3、 idea设置成eclipse的快捷键&#xff1b;4、 设置提示词忽略大小写&#xff1b;5、 关闭代码检查&#xff1b;6、 设置文档注释模板&#xff1b;7、 显示方法分隔符&#xff1b;8、 设置多行…

DAOS低时延与高性能RDMA网络

什么是RDMA RDMA&#xff08;Remote Direct Memory Access&#xff09;远程直接内存访问是一种技术&#xff0c;它使两台联网的计算机能够在主内存中交换数据&#xff0c;而无需依赖任何一台计算机的处理器、缓存或操作系统。与基于本地的直接内存访问 ( DMA ) 一样&#xff0c…

C++ Qt属性Property使用介绍

文章目录 C++ 普通类使用Qt属性Qt窗口类使用属性自定义控件使用属性在 Qt 中,属性(Property)系统提供了一种机制来定义对象的属性。这些属性可以在运行时动态地查询和修改,而且可以通过设计师工具进行可视化编辑。Qt 属性系统还支持属性绑定、动画和其他高级特性。 属性在…

DFS连通块问题

DFS连通块问题 只要是位置的上下左右都有 ‘1’ 的就是同一块连通块 一般题目类型有&#xff1a; 连通块中数量最多的连通块联通块的数量 110010000011111110101001001001101010111011011011101001111110 010000000001010001101100000010010110001111100010101100011110 00…

5-linux-用户操作、su和sudo、普通权限、特殊权限、解压压缩、软件管理(rpm和yum)、源码安装nginx、系统服务、进程管理

1 用户操作 2 su和sudo 3 普通权限 4 特殊权限 5 解压压缩 6 软件管理&#xff0c;rpm和yum 6.1 rpm 6.2 yum 7 源码安装nginx 8 系统服务 9 进程管理 1 用户操作 ####创建用户####1 创建sa和sutdents组 groupadd sa groupadd students # 2 用户可以属于多个组&#xff0c;只…