5.Redission
5.1 前文锁问题
基于 setnx 实现的分布式锁存在下面的问题:
重入问题:重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如 HashTable 这样的代码中,他的方法都是使用 synchronized 修饰的,假如他在一个方法内,调用另一个方法,那么此时如果是不可重入的,不就死锁了吗?所以可重入锁他的主要意义是防止死锁,我们的 synchronized 和 Lock 锁都是可重入的。
不可重试:是指目前的分布式只能尝试一次,我们认为合理的情况是:当线程在获得锁失败后,他应该能再次尝试获得锁。
超时释放:我们在加锁时增加了过期时间,这样的我们可以防止死锁,但是如果卡顿的时间超长,虽然我们采用了 lua 表达式防止删锁的时候,误删别人的锁,但是毕竟没有锁住,有安全隐患
主从一致性: 如果 Redis 提供了主从集群,当我们向集群写数据时,主机需要异步的将数据同步给从机,而万一在同步过去之前,主机宕机了,就会出现死锁问题。
5.2 Redission
Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的 Java 常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
Redission 提供了分布式锁的多种多样的功能
5.2.1 快速入门
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version>
</dependency>
@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient(){// 配置Config config = new Config();config.useSingleServer().setAddress("redis://192.168.150.101:6379").setPassword("123321");// 创建RedissonClient对象return Redisson.create(config);}
}
@Resource
private RedissionClient redissonClient;@Test
void testRedisson() throws Exception{//获取锁(可重入),指定锁的名称RLock lock = redissonClient.getLock("anyLock");//尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位boolean isLock = lock.tryLock(1,10,TimeUnit.SECONDS);//判断获取锁成功if(isLock){try{System.out.println("执行业务");}finally{//释放锁lock.unlock();}}}
5.2.2 注入 RedissonClient
@Resource
private RedissonClient redissonClient;@Override
public Result seckillVoucher(Long voucherId) {// 1.查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2.判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {// 尚未开始return Result.fail("秒杀尚未开始!");}// 3.判断秒杀是否已经结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {// 尚未开始return Result.fail("秒杀已经结束!");}// 4.判断库存是否充足if (voucher.getStock() < 1) {// 库存不足return Result.fail("库存不足!");}Long userId = UserHolder.getUser().getId();//创建锁对象 这个代码不用了,因为我们现在要使用分布式锁//SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);RLock lock = redissonClient.getLock("lock:order:" + userId);//获取锁对象boolean isLock = lock.tryLock();//加锁失败if (!isLock) {return Result.fail("不允许重复下单");}try {//获取代理对象(事务)IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);} finally {//释放锁lock.unlock();}}
5.3 可重入原理
- 在 Lock 锁中,他是借助于底层的一个 voaltile 的一个 state 变量来记录重入的状态的
- 比如当前没有人持有这把锁,那么 state=0
- 假如有人持有这把锁,那么 state=1
- 如果持有这把锁的人再次持有这把锁
- 那么 state 就会 +1
- 如果是对于 synchronized 而言,他在 c 语言代码中会有一个 count
- 原理和 state 类似,也是重入一次就加一,释放一次就 -1,直到减少成 0 时,表示当前这把锁没有被人持有。
- 在 redission 中,我们的也支持支持可重入锁
- 在分布式锁中,他采用 hash 结构用来存储锁
- 其中大 key 表示表示这把锁是否存在
- 用小 key 表示当前这把锁被哪个线程持有
- 所以接下来我们一起分析一下当前的这个 lua 表达式
- 这个地方一共有 3 个参数
- KEYS[1]:锁名称
- ARGV[1]:锁失效时间
- ARGV[2]: id + ":" + threadId; 锁的小 key
- exists: 判断数据是否存在 name:是 lock 是否存在,如果==0,就表示当前这把锁不存在
- redis.call('hset', KEYS[1], ARGV[2], 1); 此时他就开始往 redis 里边去写数据,写成一个 hash 结构
Lock{id + ":" + threadId: 1}
- 如果当前这把锁存在,则第一个条件不满足,再判断
- redis.call('hexists', KEYS[1], ARGV[2]) == 1
- 此时需要通过大 key+ 小 key 判断当前这把锁是否是属于自己的,如果是自己的,则进行
- redis.call('hincrby', KEYS[1], ARGV[2], 1)
- 将当前这个锁的 value 进行 +1,redis.call('pexpire', KEYS[1], ARGV[1])
- 然后再对其设置过期时间,如果以上两个条件都不满足,则表示当前这把锁抢锁失败,最后返回 pttl,即为当前这把锁的失效时间
- 如果小伙帮们看了前边的源码,你会发现他会去判断当前这个方法的返回值是否为 null,如果是 null,则对应则前两个 if 对应的条件,退出抢锁逻辑,如果返回的不是 null,即走了第三个分支,在源码处会进行 while(true) 的自旋抢锁。
5.4 重试和 WatchDog
抢锁过程中,获得当前线程,通过 tryAcquire 进行抢锁,该抢锁逻辑和之前逻辑相同
- 1. 先判断当前这把锁是否存在,如果不存在,插入一把锁,返回 null
- 2. 判断当前这把锁是否是属于当前线程,如果是,则返回 null
所以如果返回是 null,则代表着当前这哥们已经抢锁完毕,或者可重入完毕,但是如果以上两个条件都不满足,则进入到第三个条件,返回的是锁的失效时间,同学们可以自行往下翻一点点,你能发现有个 while(true) 再次进行 tryAcquire 进行抢锁
long threadId = Thread.currentThread().getId();
Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {return;
}
接下来会有一个条件分支,因为 lock 方法有重载方法,一个是带参数,一个是不带参数,如果带带参数传入的值是 -1,如果传入参数,则 leaseTime 是他本身,所以如果传入了参数,此时 leaseTime!= -1 则会进去抢锁,抢锁的逻辑就是之前说的那三个逻辑
if (leaseTime != -1) {return tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
如果是没有传入时间,则此时也会进行抢锁,而且抢锁时间是默认看门狗时间 commandExecutor.getConnectionManager()。getCfg().getLockWatchdogTimeout()
ttlRemainingFuture.onComplete((ttlRemaining, e) 这句话相当于对以上抢锁进行了监听,也就是说当上边抢锁完毕后,此方法会被调用,具体调用的逻辑就是去后台开启一个线程,进行续约逻辑,也就是看门狗线程
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(waitTime,commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {if (e != null) {return;}// lock acquiredif (ttlRemaining == null) {scheduleExpirationRenewal(threadId);}
});
return ttlRemainingFuture;
此逻辑就是续约逻辑,注意看 commandExecutor.getConnectionManager()。newTimeout()此方法
Method(**new** TimerTask() {},参数 2,参数 3)
指的是:通过参数 2,参数 3 去描述什么时候去做参数 1 的事情,现在的情况是:10s 之后去做参数一的事情
因为锁的失效时间是 30s,当 10s 之后,此时这个 timeTask 就触发了,他就去进行续约,把当前这把锁续约成 30s,如果操作成功,那么此时就会递归调用自己,再重新设置一个 timeTask(),于是再过 10s 后又再设置一个 timerTask,完成不停的续约
那么大家可以想一想,假设我们的线程出现了宕机他还会续约吗?当然不会,因为没有人再去调用 renewExpiration 这个方法,所以等到时间之后自然就释放了。
private void renewExpiration() {ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee == null) {return;}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;}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) {// reschedule itselfrenewExpiration();}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);
}
5.5 MutiLock 原理
为了提高 redis 的可用性,我们会搭建集群或者主从,现在以主从为例
此时我们去写命令,写在主机上,主机会将数据同步给从机,但是假设在主机还没有来得及把数据写入到从机去的时候,此时主机宕机,哨兵会发现主机宕机,并且选举一个 slave 变成 master,而此时新的 master 中实际上并没有锁信息,此时锁信息就已经丢掉了。
了解决这个问题,redission 提出来了 MutiLock 锁,使用这把锁咱们就不使用主从了,每个节点的地位都是一样的,这把锁加锁的逻辑需要写入到每一个主丛节点上,只有所有的服务器都写入成功,此时才是加锁成功,假设现在某个节点挂了,那么他去获得锁的时候,只要有一个节点拿不到,都不能算是加锁成功,就保证了加锁的可靠性。
那么 MutiLock 加锁原理是什么呢?笔者画了一幅图来说明
当我们去设置了多个锁时,redission 会将多个锁添加到一个集合中,然后用 while 循环去不停去尝试拿锁,但是会有一个总共的加锁时间,这个时间是用需要加锁的个数 \* 1500ms,假设有 3 个锁,那么时间就是 4500ms,假设在这 4500ms 内,所有的锁都加锁成功,那么此时才算是加锁成功,如果在 4500ms 有线程加锁失败,则会再次去进行重试。
相关文章:

5.Redission
5.1 前文锁问题 基于 setnx 实现的分布式锁存在下面的问题: 重入问题:重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如 HashTable 这样的代码中,他的方法都是使用 sync…...
c#数据结构 线性表篇 非常用线性集合总结
本人能力有限,使用了一些Ai的结论,如有不足还请斧正 目录 1.HashSet <> Dictionary 2.SortedSet <>提供升序方法的List 3.ArrayList<>List 4.BitArray <> Bit[] array 5.StringCollection <>List 6.StringDictionary<>Dictionary 1…...

dify 部署后docker 配置文件修改
1:修改 复制 ./dify/docker/.env.example ./dify/docker/.env 添加一下内容 # 启用自定义模型 CUSTOM_MODEL_ENABLEDtrue# 将OLLAMA_API_BASE_URL 改为宿主机的物理ip OLLAMA_API_BASE_URLhttp://192.168.72.8:11434# vllm 的 OPENAI的兼容 API 地址 CUSTOM_MODE…...

数据结构——排序(万字解说)初阶数据结构完
目录 1.排序 2.实现常见的排序算法 2.1 直接插入排序 编辑 2.2 希尔排序 2.3 直接选择排序 2.4 堆排序 2.5 冒泡排序 2.6 快速排序 2.6.1 递归版本 2.6.1.1 hoare版本 2.6.1.2 挖坑法 2.6.1.3 lomuto前后指针 2.6.1.4 时间复杂度 2.6.2 非递归版本 2.7 归并排序…...
SQLite3介绍与常用语句汇总
SQLite3简介 SQLite3是一款轻量级的、基于文件的开源关系型数据库引擎,由 D. Richard Hipp 于 2000 年首次发布。它遵循 SQL 标准,但与传统的数据库系统不同,SQLite 并不运行在独立的服务器进程中,而是作为一个嵌入式数据库引擎直…...

快速入门深度学习系列(3)----神经网络
本文只针对图进行解释重要内容 这就是入门所需要掌握的大部分内容 对于不懂的名词或概念 你可以及时去查 对于层数 标在上面 对于该层的第几个元素 标在下面 输入层算作第0层 对于第一层的w b 参数 维度如下w:4*3 b:4*1 这个叫做神经元 比如对于第一层的神经元 这里说的很…...

在线工具源码_字典查询_汉语词典_成语查询_择吉黄历等255个工具数百万数据 养站神器,安装教程
在线工具源码_字典查询_汉语词典_成语查询_择吉黄历等255个工具数百万数据 养站神器,安装教程 资源宝分享:https://www.httple.net/154301.html 一次性打包涵盖200个常用工具!无论是日常的图片处理、文件格式转换,还是实用的时间…...
ORB-SLAM3和VINS-MONO的对比
直接给总结,整体上orbslam3(仅考虑带imu)在初始化阶段是松耦合,localmap和全局地图优化是紧耦合。而vins mono则是全程紧耦合。然后两者最大的区别就在于vins mono其实没有对地图点进行优化,为了轻量化,它一…...
大数据处理利器:Hadoop 入门指南
一、Hadoop 是什么?—— 分布式计算的基石 在大数据时代,处理海量数据需要强大的技术支撑,Hadoop 应运而生。Apache Hadoop 是一个开源的分布式计算框架,致力于为大规模数据集提供可靠、可扩展的分布式处理能力。其核心设计理念是…...
Docker容器网络架构深度解析与技术实践指南——基于Linux内核特性的企业级容器网络实现
第1章 容器网络基础架构 1 Linux网络命名空间实现原理 1.1内核级隔离机制深度解析 1.1.1进程隔离的底层实现 通过clone()系统调用创建新进程时,设置CLONE_NEWNET标志位将触发内核执行以下操作: 内核源码示例(linux-6.8.0/kernel/fork.c&a…...
基于Kubernetes的Apache Pulsar云原生架构解析与集群部署指南(下)
文章目录 k8s安装部署Pulsar集群前期准备版本要求 安装 Pulsar Helm chart管理pulsarClustersBrokersTopic k8s安装部署Pulsar集群 前期准备 版本要求 Kubernetes 集群,版本 1.14 或更高版本Helm v3(3.0.2 或更高版本)数据持久化ÿ…...
IoTDB端边云同步技术的五大常见场景及简便使用方式
IoTDB端边云同步技术提供了一种高效、可靠的数据同步解决方案,通过简洁灵活的SQL操作和直观的配置方式,实现了数据在端、边、云之间的无缝流动。以下是IoTDB端边云同步的五大常见场景及其简便的使用方式。 一、基础数据同步 基础数据同步包括全量数据同…...

Linux 阻塞和非阻塞 I/O 简明指南
目录 声明 1. 阻塞和非阻塞简介 2. 等待队列 2.1 等待队列头 2.2 等待队列项 2.3 将队列项添加/移除等待队列头 2.4 等待唤醒 2.5 等待事件 3. 轮询 3.1 select函数 3.2 poll函数 3.3 epoll函数 4. Linux 驱动下的 poll 操作函数 声明 本博客所记录的关于正点原子…...
libtorch配置指南(包含Windows和Linux)
libtorch libtorch是pytorch的c库,提供了用于深度学习和张量计算的功能,允许开发者在c环境中使用pytorch的核心功能。特别是当一些pt模型无法转换到ncnn、mnn等模型时(ncnn、mnn可能还不支持某些层),可以在libtorch直…...

Java开发经验——阿里巴巴编码规范经验总结2
摘要 这篇文章是关于Java开发中阿里巴巴编码规范的经验总结。它强调了避免使用Apache BeanUtils进行属性复制,因为它效率低下且类型转换不安全。推荐使用Spring BeanUtils、Hutool BeanUtil、MapStruct或手动赋值等替代方案。文章还指出不应在视图模板中加入复杂逻…...

机器人手臂“听不懂“指令?Ethercat转PROFINET网关妙解通信僵局
机器人手臂"听不懂"指令?Ethercat转PROFINET网关妙解产线通信僵局 协作机器人(如KUKA iiWA)使用EtherCAT控制,与Profinet主站(如西门子840D CNC)同步动作。 客户反馈:基于Profinet…...

深度学习 CNN
CNN 简介 什么是 CNN? 卷积神经网络(Convolutional Neural Network)是专为处理网格数据(如图像)设计的神经网络。核心组件: 卷积层 :提取局部特征(如边缘、纹理)通过卷…...
GrassRoot备份项目
Windows服务项目 Grass.cs using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http.Headers; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Time…...
iOS开发架构——MVC、MVP和MVVM对比
文章目录 前言MVC(Model - View - Controller)MVP(Model - View - Presenter)MVVM(Model - View - ViewModel) 前言 在 iOS 开发中,MVC、MVVM、和 MVP 是常见的三种架构模式,它们主…...
typecho中的Widget设计文档
组成系统的最基本元素 什么是Widget Widget是组成Typecho的最基本元素,除了已经抽象出来的类库外,其它几乎所有的功能都会通过Widget来完成。在实践中我们发现,在博客这种小型但很灵活的系统中实施一些大型框架的思想是不合适的,…...

MySQL索引原理以及SQL优化(二)
目录 1. 索引与约束 1.1 索引是什么 1.2 索引的目的 1.3 索引分类 1.3.1 数据结构 1.3.2 物理存储 1.3.3 列属性 1.3.4 列的个数 1.4 主键的选择 1.5 索引使用场景 1.6 索引的底层实现 1.6.1 索引存储 1.6.2 页 1.6.3 B 树 1.6.4 B 树层高问题 1.6.5 自增 id 1.7 innod…...

MATLAB中矩阵和数组的区别
文章目录 前言环境配置1. 数据结构本质2. 运算规则(1)基本运算(2)特殊运算 3. 函数与操作4. 高维支持5. 创建方式 前言 在 MATLAB 中,矩阵(Matrix) 和 数组(Array) 的概…...

Desfire Ev1\Ev2\Ev3卡DES\3K3DES\AES加解密读写C#示例源码
本示例使用的发卡器:https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.1d292c1bYhsS9c&ftt&id917152255720 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using S…...

MySQL核心内容【完结】
MySQL核心内容 文章目录 MySQL核心内容1.MySQL核心内容目录2.MySQL知识面扩展3.MySQL安装4.MySQL配置目录介绍Mysql配置远程ip连接 5.MySQL基础1.MySQL数据类型1.数值类型2.字符串类型3.日期和时间类型4.enum和set 2.MySQL运算符1.算数运算符2.逻辑运算符3.比较运算符 3.MySQL完…...

C++类和对象进阶 —— 与数据结构的结合
🎁个人主页:工藤新一 🔍系列专栏:C面向对象(类和对象篇) 🌟心中的天空之城,终会照亮我前方的路 🎉欢迎大家点赞👍评论📝收藏⭐文章 文章目录 […...

Django之账号登录及权限管理
账号登录及权限管理 目录 1.登录功能 2.退出登录 3.权限管理 4.代码展示合集 这篇文章, 会讲到如何实现账号登录。账号就是我们上一篇文章写的账号管理功能, 就使用那里面已经创建好的账号。这一次登录, 我们分为三种角色, 分别是员工, 领导, 管理员。不同的角色, 登录进去…...
从一城一云到AI CITY,智慧城市进入新阶段
AI将如何改变城市面貌?AI能否为城市创造新的商业价值?AI的落地应用将对日常生活有什么样的影响? 几乎在每一场和城市发展相关的论坛上,都会出现以上几个问题。城市既是AI技术创新融合应用的综合性载体,普罗大众对AI产…...
Oracle数据库DBF文件收缩
这两天新部署了一套系统,数据库结构保持不变,牵扯导出表结构还有函数,图省事就直接新建用户,还原数据库了。然后咔咔咔,一顿删除delete,truncate,发现要不就是表删了,还有num_rows&a…...

EXCEL中嵌入其他表格等文件
在EXCEL中嵌入其他表格 先放链接:https://jingyan.baidu.com/article/295430f11708c34d7e00509a.html 步骤如下: 1、打开一个需要嵌入新表格的excel表。 2、切换至“插入”菜单中,单击选择“对象”。 3、如下图所示,会弹出“对象…...

21. LangChain金融领域:合同审查与风险预警自动化
引言:当AI成为24小时不眠的法律顾问 2025年某商业银行的智能合同系统,将百万级合同审查时间从平均3周缩短至9分钟,风险条款识别准确率达98.7%。本文将基于LangChain的金融法律框架,详解如何构建合规、精准、可追溯的智能风控体系…...