redis面试(七)初识lua加锁脚本
redisson
redisson如何来进行redis分布式锁实现的源码,基于redis实现各种各样的分布式锁的原理
https://redisson.org/ 这是官网
https://github.com/redisson/redisson/wiki/Table-of-Content 这是官方文档
开始 demo
- 建一个普通的工程
- 在pom.xml里引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.8.1</version>
</dependency>
- 参照官网构建RedissonClient,同时看看对应的配置
由于要在测试多节点,所以我再两台虚拟机,配置了一下redis cluster集群,标准的三主三从配置。
Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://192.168.1.1:7001")
.addNodeAddress("redis://192.168.1.1:7002")
.addNodeAddress("redis://192.168.1.1:7003")
.addNodeAddress("redis://192.168.2.2:7001")
.addNodeAddress("redis://192.168.2.2:7002")
.addNodeAddress("redis://192.168.2.2:7003");RedissonClient redisson = Redisson.create(config);
- 简单用一下分布式锁的功能
RLock lock = redisson.getLock("anyLock");
lock.lock();
lock.unlock();RMap<String, Object> map = redisson.getMap("anyMap");
map.put("foo", "bar"); map = redisson.getMap("anyMap");
System.out.println(map.get("foo"));
getLock()
点进getLock方法,可以看到如下
@Override
public RLock getLock(String name) {
return new RedissonLock(connectionManager.getCommandExecutor(), name);
}
getLock()方法的时候,获取到的Lock对象是RedissonLock对象,就可以了,里面封装了一个ConnectionManager里获取的一个CommandExecutor,CommandExecutor是什么东西?
既然是Connection开头的,里面一定是封装了一个跟redis之间进行通信的一个Cconnection连接对象,CommandExecutor,命令执行器,封装了一个redis连接的命令执行器,可以执行一些set、get redis的一些操作,用来执行底层的redis命令的
RedissonLock
在这个RedissonLock的构造函数里面,建议大家关注的一行代码,别的没什么,主要是一个internalLockLeaseTime的东西,跟watchdog看门狗有关系的
我们在RedissonLock里面打几个断点
还有一个是lock()和unlock()方法,既然调用了,肯定要看一下
再往后走到这里,可以看到,在默认情况下,加锁的时候,long leaseTime, TimeUnit unit,是没有的,-1和null,就代表着说,只要你加到了一把锁,就一定会永久性的持有这把锁,除非是你当前持有这把锁的机器宕机了,watchdog看门狗就会发现,然后就会释放锁,避免说永久性的一个死锁发生
再去下面142行看看tryAcquire()做了什么
这个里面的方法tryLockInnerAsync() 这个是核心
tryLockInnerAsync() 加锁lua脚本
先仔细看看这里面的一块绿色代码,明显不是java,其实这就是redis的lua脚本
来分析一下
if (redis.call(‘exists’, KEYS[1]) == 0) then,KEYS[1]一看就是我们设置的那个锁的名字,人家先执行了redis的exists的指令,判断一下,如果“anyLock”这个key不存在,那么就进行加锁,实际加锁的指令
"redis.call(‘hset’, KEYS[1], ARGV[2], 1); " +
"redis.call(‘pexpire’, KEYS[1], ARGV[1]); " +
"return nil; " +
hset,redis的一个指令,相当于是在redis的一个map数据结构里设置一个key value
这个map对象的名字是我们传的anyLock,然后里面的k-v键值对,k我们假设为lockState,v值呢,设置为了1
hset KEYS[1] ARGV[2] 1 转换我们传的指令后就是 hset anyLock lockState 1
pexpire KEYS[1] ARGV[1],设置一个key的过期时间
KEYS[1]其实可以理解为就是我们设置的那个key,“anyLock”,ARGV[1]其实就是一个这个key的过期时间,可能是默认的一个值,叫做30000毫秒,30s,很有可能是说的是,这个anyLock这个key对应的过期时间就是30秒
再往下的逻辑"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; " +
判断名字为anyLock的这个map对象里面某个k-v键值对,key键假设为lockState,他的对应值是不是1,(上面的逻辑中可以看到),如果存在的话就把这个值用命令hincrby +1
hincrby KEYS[1] ARGV[2] 1,将anyLock这个map中的lockState这个key的值累加1
pexpire’, KEYS[1], ARGV[1]是又把这个对象设置了一下过期时间
最后的return redis.call(‘pttl’, KEYS[1]); 是返回当前还有多久就过期了
那总结一下这个加锁逻辑,我们都知道 redis中的map数据结构是键值对。
那么这个加锁的时候,就是先判断 我们定义的锁名称“anyLock” 这个map结构是否存在,如果不存在的话,就加一个默认的键值对 k-v,key=某个默认值 value=1
如果这个名为“anyLock”的map结构已经存在了,就把那个默认的键值对k-v,key=某个默认值 value+1
commandExecutor.evalWriteAsync()
OK分析完脚本,再点进这个方法来看看
因为现在用的是redis cluster,3主3从的模式,那么这里就有是要把key放在其中某一个master上去。
这里的第一行int slot = connectionManager.calcSlot(key);
取出来redis cluster的数量,我们都知道,redis cluster集群的节点,默认是16384个,slot的数量默认就是16384个。
那这第一行的操作就是根据key的hash值,来计算出这个key要落到应该要哪个slot节点上
下一行代码就很明显了,是根据slot节点获取到,这个slot是放在哪个master上的?
anyLock这个key,13434,算出来是这个slot,相当于是针对“anyLock”这个key计算出来一个hash值,然后将这个hash值对16384这个slot数量进行取模,int slot = connectionManager.calcSlot(key);
取模之后就可以拿到当前这个“anyLock”的key对应的是哪个slot
再回到getNodeSource()方法中看看MasterSlaveEntry
MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=1, freeSubscribeConnectionsCounter=50, freeConnectionsAmount=32, freeConnectionsCounter=64, freezed=false, freezeReason=null, client=[addr=redis://192.168.1.1:7002], nodeType=MASTER, firstFail=0]]
redis://192.168.1.1:7002,编号为13434的slot所在的master是这台机器的这个端口对应的master实例
此时就是已经知道了,其实必须是将加锁的那段lua脚本,放到redis://192.168.1.1:7002这个master实例上去执行,完成加锁的操作
回过头看一下这里的代码
这里的逻辑很简单了,里面也没必要再去跟的很深了,这里大概就是上面我们分析的,拿着刚才生成的lua脚本去对应的master节点中执行脚本,把数据存入。
总结
总结一下,这里就是最基础的通过lua脚本加锁的逻辑,我们知道了redis加锁的时候是通过lua脚本将其传到redis中来加锁的。加锁的时候本质上就是新建了一个map类型的数据,key是我们的锁名称。
流程:
- 判断key是否存在
- 不存在的话新建一个map类型的数据,数据名称为锁名称
- map中的数据只有一对k-v,key应该为加锁次数,默认value为1,这个主要用来做同一个线程多次加锁的重入操作
- pexpire 命令设置过期时间,默认为30000ms 也就是30s
- 存在的话
- hincrby 命令,将map中的加锁次数 +1
- pexpire 再次将过期时间设置为30000ms 也就是30s
- pttl命令,返回当前数据的过期时间
相关文章:

redis面试(七)初识lua加锁脚本
redisson redisson如何来进行redis分布式锁实现的源码,基于redis实现各种各样的分布式锁的原理 https://redisson.org/ 这是官网 https://github.com/redisson/redisson/wiki/Table-of-Content 这是官方文档 开始 demo 建一个普通的工程在pom.xml里引入依赖 <…...

企元数智百年营销史的精粹:借鉴历史创造未来商机
随着时代的发展和科技的进步,传统营销方式正在经历前所未有的颠覆和改变。在这个数字化时代,企业需要不断创新,同时借鉴百年营销史的精粹,汲取历史经验,创造未来商机。而"企元数智"作为现代营销的代表&#…...
Java @SpringBootTest注解用法
SpringBootTest 是 Spring Framework 中的一个注解,用于指示 Spring Boot 应用程序的测试类。当你在测试类上使用 SpringBootTest 注解时,Spring Boot 会启动一个 Spring 应用程序上下文,并且加载应用程序的 application.properties 或 appli…...

构建智能招聘平台:人才招聘系统源码开发指南
本篇文章,小编将详细探讨如何基于人才招聘系统源码开发一个智能招聘平台,为企业的人才战略提供支持。 一、智能招聘平台的核心功能 智能招聘平台的核心在于提高招聘效率和匹配度,这需要集成多个关键功能模块: 1.职位发布与管理…...

Docker + Nacos + Spring Cloud Gateway 实现简单的动态路由配置修改和动态路由发现
1.环境准备 1.1 拉取Nacos Docker镜像 从Docker Hub拉取Nacos镜像: docker pull nacos/nacos-server:v2.4.01.2 生成密钥 你可以使用命令行工具生成一个不少于32位的密钥。以下是使用 OpenSSL 生成 32 字节密钥的示例: openssl rand -base64 321.3 …...

Linux中多线程压缩软件 | Mingz
原文链接:Linux中多线程压缩软件 本期教程 软件网址: https://github.com/hewm2008/MingZ安装: git clone https://github.com/hewm2008/MingZ.git cd MingZ make cd bin ./mingz -h使用源码安装: 若是你的git无法使用安装&am…...

【JavaEE精炼宝库】网络原理基础——UDP详解
文章目录 一、应用层二、传输层2.1 端口号:2.2 UDP 协议:2.2.1 UDP 协议端格式:2.2.2 UDP 存在的问题: 2.3 UDP 特点:2.4 基于 UDP 的应用层协议: 一、应用层 我们 Java 程序员在日常开发中,最…...
【回眸】周中WLB-个人
生活 计划 苏州or杭州or舟山 负负得正 烟火 鲜芋仙 办上海银行的银行卡 申请表材料准备好 个人博客提现签约变现 个人提升 yas补直播笔记(听、口)1~3课 *2倍 dy学堂 —— 3课时输出博客 个人笔记本搭建环境 副业探索 收集信息差 目前已…...

基于Spring boot + Vue的灾难救援系统
作者的B站地址:程序员云翼的个人空间-程序员云翼个人主页-哔哩哔哩视频 csdn地址:程序员云翼-CSDN博客 1.项目技术栈: 前后端分离的项目 后端:Springboot MybatisPlus 前端:Vue ElementUI 数据库: …...
C#进阶:轻量级ORM框架Dapper详解
C#进阶:轻量级ORM框架Dapper详解 在C#开发中,ORM(对象关系映射)框架是处理数据库交互的重要工具。Dapper作为一个轻量级的ORM框架,专为.NET平台设计,因其高性能和易用性而备受开发者青睐。本文将详细介绍D…...

【python015】常见成熟AI-图像识别场景算法清单(已更新)
1.欢迎点赞、关注、批评、指正,互三走起来,小手动起来! 【python015】常见成熟AI-图像识别场景算法清单及代码【python015】常见成熟AI-图像识别场景算法清单及代码【python015】常见成熟AI-图像识别场景算法清单及代码 文章目录 1.背景介绍2…...
删除有序数组中的重复项(LeetCode)
题目 给你一个 升序排列 的数组 ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 中唯一元素的个数。 考虑 的唯一元素的数量为 ,你需要做以下事情确…...

【算法 03】雇佣问题
“雇用问题”及其算法优化 在日常生活和工作中,我们经常会遇到需要从多个选项中做出选择的情况,而“雇用问题”正是这样一个典型的例子。在这个问题中,我们不仅要考虑如何高效地找到最佳候选人,还要关注整个过程中的成本。今天&a…...

vue3+axios请求导出excel文件
在Vue 3中使用axios请求导出Excel文件,可以发送一个GET或POST请求,并设置响应类型为blob或arraybuffer,然后使用new Blob()构造函数创建一个二进制文件,最后使用URL.createObjectURL()生成一个可以下载的链接。 先看代码 import…...
LLM与NLP
大语言模型与自然语言处理的关系:整体与组成的关系如 自然语言理解的编码器式(encoder-only)的架构是语境相关的词表示BERT; 自然语言转换的编码器-解码器式的(encoder-decoder)的架构是词频-逆文档词频T…...
js 判断是否为回文串
需求:忽略英文大小写和空格差异,判断是否为回文字符串(例如"我爱你 你爱我","abc bA") 思路:利用翻转字符串比较,利用循环双指针,利用递归或者双循环…...
多重背包c++
题目描述 有N种物品和一个容量是V的背包。 第i种物品最多有si件,每件体积是vi,价值是wi。 求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。 输出最大价值。 输入 第一行两个整数,N&#x…...
kernel input事件测试程序
测试内核input 事件测试程序。 getevent -lt 命令查看注册的是是event0/1/2/3/4 中的哪一个。 gcc input_test.c -o input_test 编译成可执行程序。将编译的input_test,U盘或ADB push到系统里面,chmod 777 input_test 在 ./input_test input_test.c #…...
gd32 i2c 中断 主机从机双向通信例程
Master I2C0_SCL PB8 AF4 I2C0_SDA PB9 AF4 Slave I2C1_SCL PB10 AF4 I2C1_SDA PB11 AF4 //主机中断发送 void i2c_master_transmit_it(uint32_t address, uint8_t* buff, uint32_t size); //主机中断接收 void i2c_master_receive_it(uint32_t address, uint8_t* buff, uint…...
程序员在AI时代:重塑核心竞争力,共舞智能未来
程序员在AI时代:重塑核心竞争力,共舞智能未来 在这个日新月异的科技时代,人工智能生成内容(AIGC)技术,尤其是以ChatGPT、Midjourney、Claude等为代表的大语言模型,正以前所未有的速度渗透到编程…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
第三周 Day 3 🎯 今日目标 理解类(class)和对象(object)的关系学会定义类的属性、方法和构造函数(init)掌握对象的创建与使用初识封装、继承和多态的基本概念(预告) &a…...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...