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,一经查实,立即删除!

相关文章

企业微信小群发公告的接口和功能测试

企业微信小群发公告的接口和功能测试&#xff0c;可以从以下几个方面进行&#xff1a; 功能测试&#xff1a; 1.验证发送公告的基础功能。例如&#xff0c;发送公告是否能够被成功发送到小群中&#xff0c;公告内容是否能够被所有群成员看到。 2.验证公告的多种设置功能&…

C# 使用FluentScheduler触发定时任务

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

对于技术人员实力的判断

技术实力的迷思 俗话说“文无第一&#xff0c;武无第二”&#xff0c;技术就是一种“文”的能力&#xff0c;很多时候我们很难直观看出一个技术人员的实力&#xff0c;但不管是公司招聘的面试&#xff0c;还是公司内部的晋升面评&#xff0c;都需要在较短时间内快速判断一个技…

Linux——进程状态

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

excel xla文件怎么导入到excel

在Excel中导入XLA文件&#xff08;Excel Add-In&#xff09;的步骤如下&#xff1a; 1. **打开Excel&#xff1a;** 启动 Microsoft Excel 应用程序。 2. **选择“文件”选项卡&#xff1a;** 点击Excel窗口左上角的“文件”选项卡。 3. **选择“选项”&#xff1a;** 在文件…

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

系列文章目录 什么是计算机网络&#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; …

Android 默认打开应用的权限

有项目需要客户要安装第三方软件&#xff0c;但是要手动点击打开权限&#xff0c;就想不动手就打开。 //安装第三方软件&#xff0c;修改方式 frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java //找到如下源码&#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;如果二叉…

《算法面试宝典》--机器学习常见问题汇总

第二章 机器学习基础 ========================= ​ 机器学习起源于上世纪50年代,1959年在IBM工作的Arthur Samuel设计了一个下棋程序,这个程序具有学习的能力,它可以在不断的对弈中提高自己。由此提出了“机器学习”这个概念,它是一个结合了多个学科如概率论,优化理论,…

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

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

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

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

AV1(AOMedia Video 1) 编程指南

AV1&#xff08;AOMedia Video 1&#xff09;是一种开源、免专利费的视频编码格式&#xff0c;由AOMedia&#xff08;开放媒体联盟&#xff09;开发。AV1旨在提供比现有的视频编解码格式&#xff08;如H.264/AVC和HEVC&#xff09;更高的压缩效率&#xff0c;同时保持高质量视频…

ubuntu apt指令集学习心得

ubuntu apt指令集学习心得 在Ubuntu中&#xff0c;我们可以使用以下apt指令集来管理软件包&#xff1a; 1. 更新软件包列表&#xff1a; sudo apt update 2. 升级已安装的软件包&#xff08;将系统中的所有软件包升级到最新版本&#xff09;&#xff1a; s…

Linux:缓冲区的概念理解

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

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

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

微服务架构之服务发现

在微服务架构中&#xff0c;服务与服务之间需要通过服务发现来找到对方&#xff0c;以便发起请求。 所谓的服务发现就是一种自动监测并发现网络内的的服务的机制。可以调用者动态感知到网络上服务的变化。DNS就是最典型的服务发现系统了。 在微服务架构中的服务发现的工作原理…

【动态规划】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、测试框架简介 …