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.hqwc.cn/news/256995.html

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

相关文章

Volumetric Lights 2 HDRP

高清晰度渲染管道,包括先进的新功能,如半透明阴影图和直接灯光投射加上许多改进。 插件是一个快速,灵活和伟大的前瞻性光散射解决方案的高清晰度渲染管道。只需点击几下,即可改善场景中的照明视觉效果。 兼容: 点光源 聚光灯 碟形灯 矩形灯 通过覆盖摄像机周围大面积区域的…

nacos服务的分级存储

举例说明 一个服务可以有多个实例&#xff0c;比如我们当前有4个实例&#xff0c;都叫做nacos-user-service服务 ip地址端口服务器所属地区集群192.168.xxx.xxx18080广东GD192.168.xxx.xxx18081广东GD192.168.xxx.xxx18082广西GX192.168.xxx.xxx18083广西GX所以我们可以将nacos…

十五届蓝桥杯分享会(一)

注&#xff1a;省赛4月&#xff0c;决赛6月 一、蓝桥杯整体介绍 1.十四届蓝桥杯软件电子赛参赛人数&#xff1a;C 8w&#xff0c;java/python 2w&#xff0c;web 4k&#xff0c;单片机 1.8w&#xff0c;嵌入式/EDA5k&#xff0c;物联网 300 1.1设计类参赛人数&#xff1a;平…

12月7日作业

使用QT模仿一个登陆界面&#xff08;模仿育碧Ubisoft登录界面&#xff09; #include "myqq.h"MyQQ::MyQQ(QWidget *parent): QMainWindow(parent) {this->resize(880,550); //设置窗口大小this->setFixedSize(880,550); //固定窗口大小this->setStyleShee…

时间序列预测实战(二十四)PyTorch实现RNN进行多元和单元预测(附代码+数据集+完整解析)

一、本文介绍 本篇文章给大家带来的是利用我个人编写的架构进行RNN时间序列卷积进行时间序列建模&#xff08;专门为了时间序列领域新人编写的架构&#xff0c;简单且不同于市面上大家用GPT写的代码&#xff09;&#xff0c;包括结果可视化、支持单元预测、多元预测、模型拟合…

MySQL实战45讲-第1-2讲-一条SQL查询语句是如何执行的? 一条SQL更新语句是如何执行的

大体来说&#xff0c;MySQL可以分为Server层和存储引擎层两部分 Server层&#xff1a;Server层包括连接器、查询缓存、分析器、优化器、执行器等。以及所有的内置函数&#xff08;如日期、时间、数学和加密函数等&#xff09;&#xff0c;所有跨存储引擎的功能都在这一层实现&a…

探索 HTML 语义化:让你的网页更有意义(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

1、初识 llvm源码编译 及virtualbox和ubuntu环境搭建

很久没更新了&#xff0c;最近准备研究逆向和加固&#xff0c;于是跟着看雪hanbing老师学习彻底搞懂ollvm&#xff0c;终于把所有流程跑通了&#xff0c;中间遇到了太多的坑&#xff0c;所以必须记录一下&#xff0c;能避免自己和帮助他人最好。 环境搭建太重要了&#xff0c;…

6.1 U-boot的使用

由于Ubuntu出现了一些问题&#xff0c;后面都是使用正点原子官方版本。 一、U-boot使用 1. U-boot源码 Linux 系统要启动需要通过 bootloader 程序引导&#xff0c;也就说芯片上电以后先运行一段 bootloader 程序。这段 bootloader 程序会先初始化 DDR 等外设&#xff0c;然后…

Smart Link和Monitor Link

Smart Link和Monitor Link简介 Smart Link&#xff0c;又叫做备份链路。一个Smart Link由两个接口组成&#xff0c;其中一个接口作为另一个的备份。Smart Link常用于双上行组网&#xff0c;提供可靠高效的备份和快速的切换机制。 Monitor Link是一种接口联动方案&#xff0c;它…

2024黑龙江省职业院校技能大赛信息安全管理与评估赛项规程

2024黑龙江省职业院校技能大赛暨国赛选拔赛 “GZ032信息安全管理与评估”赛项规程 极安云科专注技能竞赛&#xff0c;包含网络建设与运维和信息安全管理与评估两大赛项&#xff0c;及各大CTF&#xff0c;基于两大赛项提供全面的系统性培训&#xff0c;拥有完整的培训体系。团队…

ComplexHeatmap热图专栏 | 6. 3D热图绘制教程

本期教程 原文链接https://mp.weixin.qq.com/s/EyBs6jn78zOomcTv1aP52g 6 3D热图的绘制教程 基于《热图绘制教程》专栏&#xff0c;本教程已更新了5个章节&#xff0c;不知道大家是否有所收获。对于小杜个人来说&#xff0c;真的需要不断的复习和练习才可以记住&#xff0c;但…