Redission分布式锁原理初探

什么是分布式锁,为什么需要分布式锁

在多线程并发请求当中,为了保证我们的资源同一时刻只有一个线程进行操作(如商品超卖问题、购票系统等),我们通常要添加锁机制,如ReentrantLock,也就是可重入的互斥锁,与synchronized功能类似,因为比较灵活,所以经常使用。这在单机情况下是没有问题的,但在多节点的情况下,也就意味着有多个进程,ReentrantLock锁机制可能就会不起作用,所以我们需要一种能够跨进程的锁,也就是同一时刻只能让一个进程获取锁,来控制共享资源的访问。

分布式锁有哪些实现方式

  • 基于数据库分布式锁(悲观锁如:select xxx for update、乐观锁如version版本号机制)
  • 基于 Redis 实现分布式锁
  • 基于分布式协调服务 ZooKeeper 实现分布式锁

核心也是使用了每个节点都会用到的第三方组件,例如mysql、redis、zookeeper

Redis 实现分布式锁

使用setnx命令,在 Redis 中,setnx 命令是可以帮助我们实现互斥,setnx 即 set if not exists (对应 Java 中的 setIfAbsent 方法),如果 key 不存在的话,会设置 key 的值,如果 key 已经存在, 则啥也不做

if(redisTemplate.opsForValue().setIfAbsent(key, value , time, TimeUnit)){ //加锁try {do something  //业务处理}catch(){}finally {// 释放锁String delVal = valueOperations.get(key).toString();if (value.equals(delVal)){redisTemplate.delete(key);}}
}

通常情况下我们一般使用setnx + expire来实现防止死锁,但仍然会有锁被别的线程误删的问题(查询,删除不是一个原子操作,会有并发问题)

为什么会有锁被别的线程误删?假如线程A和线程B都执行同一段代码进行加锁,线程A加锁成功,当出现业务执行时间过长,超过了过期时间,这时线程A释放了锁,此时线程B就能加锁成功,接下来执行线程B业务操作,这个时候线程A业务操作执行完了,在finally方法中执行delete key,这个时候线程A就会把线程B的锁给释放了。

所以一般释放的锁的时候,最好使用lua脚本来进行释放,来实现原子性的查询,比较,并删除锁。

if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) 
elsereturn 0
end;

lua脚本的话可以保证我们执行的时候多个命令执行期间不回被其他线程打断,或出现竞争状态,也就是可以看作一次请求,保证了我们命令的原子性
但这个方案仍然有个缺点:锁过期释放了,业务还没执行完。对于可能存在锁过期释放,业务没执行完的问题。我们可以稍微把锁过期时间设置长一些,让其大于正常业务处理时间。如果你觉得不是很稳,还可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放(如每5秒查看一下锁的过期时间,如果小于10秒,就延期),针对这种锁续约机制,redission框架就帮我们解决了这个问题

基于Redisson的分布式锁的实现

首先redission使用方式也比较简单

  1. 引入依赖
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.17.0</version>
</dependency> 
  1. 写redisson的配置类
@Bean
public RedissonClient redissonClient(){...// 添加redis地址// 设置锁的超时时间// 创建 RedissonClient 对象
}
  1. 使用redissonClient客户端加锁
@Autowired
private RedissonClient redissonClient;public void test() throws InterruptedException {RLock lock = redissonClient.getLock("anyLock");boolean locked = lock.tryLock(1,10,TimeUnit.SECONDS);// 参数:1.获取锁的最大等待时间(期间会重试),2.锁自动释放时间,3.时间单位if(locked){try{// 业务操作}finally{//释放锁lock.unlock();}}
}

假如我们加锁没有传参数直接使用tryLock(),Redisson则会设置默认的锁过期时间为30s,并且如果任务超过了30s还没有执行完毕,则后台会有一个线程,默认没隔10s执行task,重置过期时间,也就是WatchDog机制
redisson看门狗自动续期源码
在这里插入图片描述

private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFuture<Long> ttlRemainingFuture;if (leaseTime != -1) {ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}ttlRemainingFuture.onComplete((ttlRemaining, e) -> {if (e != null) {return;}// lock acquiredif (ttlRemaining == null) {if (leaseTime != -1) {internalLockLeaseTime = unit.toMillis(leaseTime);} else {scheduleExpirationRenewal(threadId);}}});return ttlRemainingFuture;}

tryAcquireAsync方法:

  • 如果没有设置过期时间,就会执行默认的过期时间:lockWatchdogTimeout = 30 * 1000(ms)
  • 执行回调函数即看门狗机制
protected void scheduleExpirationRenewal(long threadId) {ExpirationEntry entry = new ExpirationEntry();// 将线程放入缓存操作ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);if (oldEntry != null) {// 如果已经有该线程,则不再延期oldEntry.addThreadId(threadId);} else {entry.addThreadId(threadId);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;}// 执行续约的lua脚本RFuture<Boolean> future = renewExpirationAsync(threadId);future.onComplete((res, e) -> {if (e != null) {log.error("Can't update lock " + getRawName() + " expiration", e);EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}if (res) {// 延期成功,回调自己,继续续约renewExpiration();}});}// 每隔internalLockLeaseTime/3=10秒检查一次}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);}// -----------------------------------------------------------------------
protected RFuture<Boolean> renewExpirationAsync(long threadId) {return evalWriteAsync(getRawName(), 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(getRawName()),internalLockLeaseTime, getLockName(threadId));}

关键方法:renewExpiration()

  • 函数开启了一个定时任务,在10s后执行,并且会在调用成功后,再次调用“自己”,即续约机制
  • 可以看到Redisson也是使用Lua脚本进行锁续约的,lua脚本里会进行判断:锁是否存在,如果存在则重置过期时间为30s

最后关于Redisson:
Redisson是Java的redis客户端之一,提供了一些api方便操作redis。锁只是它的一个工具类,其他还包括分布式对象、分布式集合等等,详细可参考:https://github.com/redisson/redisson/wiki/

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

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

相关文章

C# 使用FluentScheduler触发定时任务

写在前面 FluentScheduler是.Net平台下的一个自动任务调度组件&#xff0c;以前经常用的是Quarz.Net&#xff0c;相对而言FluentScheduler的定时配置更为直观&#xff0c;可直接用接口进行参数化设置&#xff0c;对Cron表达式有恐惧症的人来说简直就是福音&#xff0c;使用起来…

Linux——进程状态

我们都知道进程信息被放到了PCB&#xff08;task_struct&#xff09;中&#xff0c;可以理解为进程属性的集合。 PCB中包含了进程的ID&#xff0c;时间片&#xff0c;pc指针&#xff0c;所有的寄存器&#xff0c;进程状态、优先级、I/O状态信息等等...有兴趣的可以去看看源码&…

【计算机网络笔记】物理层——频带传输基础

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

LAMP和分离式LNMP部署

目录 一.什么是LAMP&#xff1f; 二.安装LAMP 先安装apache&#xff0c;httpd网页服务&#xff1a; 接着安装mysql&#xff1a; 安装php&#xff1a; 创建论坛&#xff1a; 三.安装分布式LNMP&#xff1a; 先安装nginx&#xff1a; 到另一台主机安装php&#xff1a; …

整数二分的建模

当题目能够使用整数二分法建模时&#xff0c;主要有整数二分法思想进行判定&#xff0c;它的基本形式如下&#xff1a; while(left < right) {int ans;//记录答案 int mid left (right - left) / 2;//二分if(check(mid)){//检查条件&#xff0c;如果成立 ans mid;//记录…

Python实现的二叉树的先序、中序、后序遍历示例

一、先序、中序、后序遍历的次序&#xff1a; 创建好一棵二叉树后&#xff0c;可以按照一定的顺序对树中所有的元素进行遍历。按照先左后右&#xff0c;树 的遍历方法有三种&#xff1a;先序遍历、中序遍历和后序遍历。 其中&#xff0c;先序遍历的次序是&#xff1a;如果二叉…

用23种设计模式打造一个cocos creator的游戏框架----(一)生成器模式

1、模式标准 模式名称&#xff1a;生成器模式 模式分类&#xff1a;创建型 模式意图&#xff1a;将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 结构图&#xff1a; 适用于&#xff1a; 当创建复杂对象的算法应该独立于该对象的…

[MySQL--基础]事务的基础知识

前言 ⭐Hello!这里是欧_aita的博客。 ⭐今日语录&#xff1a;生活中最重要的决定就是要做出决定。 ⭐个人主页&#xff1a;欧_aita ψ(._. )>⭐个人专栏&#xff1a; 数据结构与算法 MySQL数据库 事务的目录&#x1f4d5; 前言事务简介&#x1f680;事务操作&#x1f680;准…

Linux:缓冲区的概念理解

文章目录 缓冲区什么是缓冲区&#xff1f;缓冲区的意义是什么&#xff1f;缓冲区的刷新方式 理解缓冲区用户缓冲区和内核缓冲区缓冲区在哪里&#xff1f; 本篇主要总结的是关于缓冲区的概念理解&#xff0c;以及再次基础上对文件的常用接口进行一定程度的封装 缓冲区 什么是缓…

keil添加了头文件仍然报找不到头文件的原因

如图&#xff0c;我在user分组新建Item&#xff0c;可是keil提示头文件不存在&#xff0c;所有的一切设置都是对的&#xff0c;但就是找不到头文件&#xff0c;找了很久&#xff0c;最后才发现是user分组和文件系统中的文件夹不一致的原因。 如图&#xff0c;在分组的文件系统的…

【动态规划】03使用最小花费爬楼梯(easy1)

题目链接&#xff1a;leetcode使用最小花费爬楼梯 目录 题目解析&#xff1a; 算法原理 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 编写代码 题目解析&#xff1a; 题目让我们求达到楼梯顶部的最低花费. 由题可得&#xff1a; cost[i] 是从楼梯第 i 个…

【附源码】完整版,Python+Selenium+Pytest+POM自动化测试框架封装

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、测试框架简介 …

【VRTK】【VR开发】【Unity】11-甩臂移动

课程配套学习资源下载 https://download.csdn.net/download/weixin_41697242/88485426?spm=1001.2014.3001.5503 【概述】 除了一般的移动能力,VRTK还提供更为沉浸的甩臂移动。 【设定摇杆输入中间件】 在Hierarchy中展开Button Input Actions,其下生成两个新的空子对象…

主动而非被动:确保网络安全运营弹性的途径

金融部门处理威胁的经验对网络安全领域的任何人都有启发——没有什么可以替代提前摆脱潜在的风险和问题。 从狂野西部的银行劫匪到勒索软件即服务 (RaaS)&#xff0c;全球金融生态系统面临的威胁多年来发生了巨大变化。技术进步带动了金融业的快速发展&#xff0c;从现金交易到…

基于轻量级MnasNet模型开发构建40种常见中草药图像识别系统

文本是前文的后续&#xff1a; 《python基于轻量级GhostNet模型开发构建23种常见中草药图像识别系统》 前文主要是在小批量小种类数据集上尝试开发构建基于轻量级CNN模型的中草药图像识别系统&#xff0c;本文的初衷是想要构建一个大类别大数据集的基础&#xff0c;但是无奈发…

【天线了解】2.WTW天线了解与使用

注意网段&#xff1a;&#xff08;计算机与设备同一网段才可以通信&#xff09; 1.LS28接收机使用的网段是192.168.16.X&#xff0c;所以电脑应该同样设置 2.WTW天线使用网段192.168.98.X 0.WTW使用原理 1.计算机控制LS28&#xff08;接收机&#xff09;&#xff0c;WTW天线。 …

全志H6-ARMLinux第1天:全志概述、刷机登陆、官方外设库、蜂鸣器、超声波测距

1. 全志H616课程概述&#xff08;456.01&#xff09; 1.1 为什么学 学习目标依然是Linux系统&#xff0c;平台是ARM架构 蜂巢快递柜&#xff0c;配送机器人&#xff0c;这些应用场景用 C51、STM32 单片机无法实现第三方介入库的局限性&#xff0c;比如刷脸支付和公交车收费设…

python的extend函数详解

文章目录 语法功能示例例1&#xff1a;添加列表例2&#xff1a;添加元组例3&#xff1a;添加集合例4&#xff1a;添加字典&#xff08;只添加键&#xff09;例5&#xff1a;添加字符串例6&#xff1a;混合类型扩展例7&#xff1a;扩展空列表或不可迭代对象 注意事项&#xff1a…

PHP使用mkcert本地开发生成HTTPS证书 PhpEnv集成环境

PHP使用mkcert本地开发生成HTTPS证书 PhpEnv集成环境 前言一、介绍 mkcert二、安装/使用 mkcert1. 安装2. 使用 总结 前言 本地开发时有些功能只有在 https 证书的情况下才能使用, 例如一些 Web API 一、介绍 mkcert Github地址 mkcert 是一个制作本地可信开发证书的简单工具。…

SmartChart:一站式数据可视化解决方案

在当今的数据驱动的世界中&#xff0c;数据可视化已经成为了一个重要的工具&#xff0c;它可以帮助我们理解复杂的数据集&#xff0c;并从中提取有价值的信息。SmartChart就是这样一个强大的数据可视化工具&#xff0c;它提供了一站式的数据可视化解决方案&#xff0c;无论你是…