分布式锁(redisson,看门狗,主从一致性)
目录
- 分布式锁
- 一:基本原理和实现方式
- 二:分布式锁的实现
- 1:分布式锁的误删问题
- 2:解决误删问题
- 三:lua脚本解决多条命令原子性问题
- 调用lua脚本
- 四:Redisson
- 1:redisson入门
- 2:redisson可重入锁的原理
- 3:解决尝试等待,超时释放的问题(看门狗)
- 4:主从一致性问题
分布式锁
一:基本原理和实现方式
使用redis实现分布式锁:
我们实现分布式锁有两个基本方法:
一个是获取锁
一个是删除锁
获取我们使用setnx,只有不存在才会set成功作为互斥锁;
删除直接删除这个锁的键就行了;
但是要考虑一种情况,就是获取锁之后服务宕机了,那么就无法释放锁,也就会出现死锁,服务挂了,为了预防这种情况的发生我们要设置过期时间,就算服务宕机,过了过期时间锁也会释放;
还有一种情况就是设置锁,还没去设置锁的过期时间这个时候服务就宕机了,那么也会出现死锁,我们要保证获取锁和设置过期时间这个操作的原子性,redis种就提供了方法可以同时设置过期时间和互斥锁:
set lock t1 nx ex 10
二:分布式锁的实现
我们去实现这个接口:
public class SimpleRedisLock implements ILock{//锁的名称private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}//锁的前缀是个常量private static final String KEY_VALUE ="lock:";/*** 获取锁* @param timeoutSec* @return*/@Overridepublic boolean trylock(long timeoutSec) {//获取当前线程id作为锁的valuelong id = Thread.currentThread().getId();//设值尝试获取锁,true是成功,false是失败Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(KEY_VALUE + name, id + "", timeoutSec, TimeUnit.SECONDS);//因为直接返回b的话jvm会自动装箱拆箱,可能会造成空指针异常,如果b为true返回就是true,如果b为false返回就是false,如果为空返回也是false;return Boolean.TRUE.equals(b);}/*** 释放锁*/@Overridepublic void unlock() {stringRedisTemplate.delete(KEY_VALUE + name);}
}
我们在一人一单的问题使用分布式锁:
Long id = UserHolder.getUser().getId();
SimpleRedisLock simpleRedisLock = new SimpleRedisLock("order" + id, stringRedisTemplate);
boolean trylock = simpleRedisLock.trylock(1200);
if (!trylock){return Result.fail("一人只能下一单");
}
try {IVoucherOrderService orderService = (IVoucherOrderService) AopContext.currentProxy();return orderService.createVoucherOrder(voucherId);
} catch (IllegalStateException e) {throw new RuntimeException(e);
}finally {simpleRedisLock.unlock();
}
1:分布式锁的误删问题
存在的问题是这样的:
线程1去获取锁,但是呢,业务阻塞超时了,锁就自动释放了,这个时候线程2来了,因为锁释放了,线程2可以获取到锁,就在这时,线程1阻塞通过了,完成了业务之后就要释放锁了,这个时候释放的锁是线程2的锁,线程2还在执行,这个时候线程3也去获取锁成功了,那么就出现了线程2,3并发执行的情况,造成并发安全问题;
这里的问题是线程释放锁释放的不是自己的锁,所以我们解决办法就是在线程释放的时候判断一下是不是自己的锁,怎么判断呢,我们之前获取锁的时候存入的value是线程的id,这个就是线程的唯一标识,我们只需要判断当前线程id和存入的value是否一致就行;
2:解决误删问题
//锁的前缀是个常量
private static final String KEY_VALUE ="lock:";
//加上线程的前缀,保证不同集群不同线程的id是唯一
public static final String ID_VALUE= UUID.randomUUID().toString(true);/*** 获取锁* @param timeoutSec* @return*/
@Override
public boolean trylock(long timeoutSec) {//获取当前线程id作为锁的valueString id =ID_VALUE+ Thread.currentThread().getId();//设值尝试获取锁,true是成功,false是失败Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(KEY_VALUE + name, id + "", timeoutSec, TimeUnit.SECONDS);//因为直接返回b的话jvm会自动装箱拆箱,可能会造成空指针异常,如果b为true返回就是true,如果b为false返回就是false,如果为空返回也是false;return Boolean.TRUE.equals(b);
}
只要在锁获取的时候给线程加上唯一标识就行,我们原来用的是线程id,这样不能保证唯一,因为线程id是在同一个jvm内部是递增的,不同jvm的线程id可能相同,那么就需要加上前缀保证线程标识的唯一性,那么就可以使用uuid来保证每一台jvm的uuid是不同的,标识就是唯一的;
释放锁:
@Override
public void unlock() {if (stringRedisTemplate.opsForValue().get(KEY_VALUE + name).equals(ID_VALUE+ Thread.currentThread().getId())){stringRedisTemplate.delete(KEY_VALUE + name);}
}
判断锁中的val和当前线程的标识是否一致;
这样就解决了误删的问题;
但是还有一个问题:
假设一个线程1获取锁之后执行业务,执行完之后,要释放锁,判断了当前锁是否是自己的(锁的value),在要执行删除操作的时候堵塞了,锁超时释放了,这个时候线程2获取锁,执行业务,就在这时,线程1阻塞结束,执行的代码在释放锁上,因为同一个业务锁的key都是一样的,所以线程一能够把锁释放,这时线程2还没执行结束,线程3来了,就出现了线程安全问题;
所以说我们要将判断锁和释放锁作为一个原子性事件,要么同时发生要么同时失败
这就要用lua了
三:lua脚本解决多条命令原子性问题
--获取当前key
local key =KEY[1]
--获取当前线程标识
local id =ARGV[1]--获取锁中的标识
local _id=redis.call('get',key)
--判断是否是同一个标识:
if(id==_id) thenreturn redis.call("del",key)
end
return 0
在idea中我们执行lua
脚本我们可以在resouce中创建一个lua文件:
要安装插件才行:
插件名叫emmylua;
调用lua脚本
//声明一个DefaultRedisScript,原来调取lua脚本
public static final DefaultRedisScript UNLOCK_LUA;
static {//初始化UNLOCK_LUA=new DefaultRedisScript<>();//定位到lua脚本的位置UNLOCK_LUA.setLocation(new ClassPathResource("unlock.lua"));//设置lua脚本的返回值UNLOCK_LUA.setResultType(Long.class);
}
@Override
public void unlock() {// Collections.singletonList创建单元素的集合stringRedisTemplate.execute(UNLOCK_LUA,Collections.singletonList(KEY_VALUE + name),(ID_VALUE+ Thread.currentThread().getId()));
}
这样将判断标识和释放锁的命令写在lua脚本中,就能够保证命令执行的原子性,就不会出现之前说的误删的情况
我们实现分布式锁的思路:
获取锁:使用setnx,互斥的特性,获取值相当于设置锁,使用uuid保证线程的唯一性;
释放锁:释放之前判断锁释放是当前线程的锁,并且将判断锁和释放锁的命令写在一个lua脚本中保证执行命令的原子性;
四:Redisson
1:redisson入门
步骤1:
引入依赖:
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.33.0</version>
</dependency>
步骤二:
配置客户端:
@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient(){Config config = new Config();config.useSingleServer().setAddress("redis://123.249.86.145:6379").setPassword("12345");return Redisson.create(config);}
}
2:redisson可重入锁的原理
可重入锁的逻辑:
获取锁:如果锁不存在就获取锁,然后设置过期时间,如果存在了,就判断锁的线程标识是否是自己,如果是自己,那么就让锁的value加一;
释放锁:释放锁的时候判断锁是否是自己的,如果不是自己的就返回空,如果是自己的就让value-1,然后再判断value是否大于0,如果大于0,就重置锁的时间,等于0就删除锁
3:解决尝试等待,超时释放的问题(看门狗)
redisson内部是如何解决重复尝试的呢?尝试解读一下:首先线程会尝试获取锁,获取锁成功就返回空,获取锁失败就返回ttl,也就是锁的超时释放时间,我们重复尝试肯定是建立在获取锁失败的基础上,当返回的是ttl时,我们会判断当前时间减去我们尝试获取锁的时刻的时间,也就是尝试获取锁使用的时间,是否大于我们设置的等待时间,如果大于我们设置的等待时间,那么就尝试获取锁失败,就返回false,如果还有时间,我们不是直接再去获取锁,而是通过订阅机制,我们等待释放锁的信号,如果等待时间超时就直接返回false,如果没抄时,我们在重新尝试锁,失败在判断是否还有时间然后定义然后重试,就这么循环下去,要么拿到锁,要么超过等待时间获取锁失败;这样就解决我们获取锁失败一次就返回失败,可以一直尝试;
然后再来讲一下超时释放问题,也就是过了设置的超时时间,业务还没执行完,锁就释放了,出现了并发安全问题,怎么解决的呢,我们如果没有设置这个锁的超时释放时间,那么redisson就会自动给我们的超时释放时间设置为-1,这个时候才会开启看门狗机制,具体是什么情况呢,就是内部会开启一个任务,这个任务会一直刷新锁的超时时间,一直无限的刷新,每10秒刷新一次,刷新一次延长30秒,也就会一直存在这个锁,知道完成任务释放锁才会取消这个看门狗任务;
那么释放锁的时候如果释放失败就返回异常,释放成功就要做两件事,一个是发送释放锁的信息给正在订阅尝试获取锁的,还有一件事就是将看门狗任务终止;
4:主从一致性问题
主从一致性问题:在redis集群中有主从关系,一个主节点有多个从节点,当主节点宕机时,会选一个从节点作为主节点;
主节点和从节点为了数据一样会做主从同步操作;有一个问题就是当java应用向主节点发送请求获取锁,获取锁成功了,但是在主从同步还没完成的时候,主节点宕机了,这个时候哨兵就会发现宕机,然后从从节点中选出一个作为主节点,而因为数据未同步,新的主节点中没有锁,也就是锁失效了,那么其他线程发送请求就能获取锁,这就是锁失效的问题
如何解决呢:我们不设置主从关系,只设置节点,每个节点都相当与主节点,而且获取锁的时候,必须要所有的节点都获取锁成功才算获取锁成功,如果有一个节点宕机了,他的从节点成为了新的节点,这个时候有线程来获取锁也是不成功的,因为要在所有节点中获取锁,而其他节点已经持有锁了;这就解决了主从一致性问题;
,获取锁成功了,但是在主从同步还没完成的时候,主节点宕机了,这个时候哨兵就会发现宕机,然后从从节点中选出一个作为主节点,而因为数据未同步,新的主节点中没有锁,也就是锁失效了,那么其他线程发送请求就能获取锁,这就是锁失效的问题
[外链图片转存中…(img-1BgOGPaG-1730426881642)]
如何解决呢:我们不设置主从关系,只设置节点,每个节点都相当与主节点,而且获取锁的时候,必须要所有的节点都获取锁成功才算获取锁成功,如果有一个节点宕机了,他的从节点成为了新的节点,这个时候有线程来获取锁也是不成功的,因为要在所有节点中获取锁,而其他节点已经持有锁了;这就解决了主从一致性问题;
相关文章:

分布式锁(redisson,看门狗,主从一致性)
目录 分布式锁一:基本原理和实现方式二:分布式锁的实现1:分布式锁的误删问题2:解决误删问题 三:lua脚本解决多条命令原子性问题调用lua脚本 四:Redisson1:redisson入门2:redisson可重…...

openEuler 服务器Python自动化安装WEB服务器和文件上传服务(1)
一、系统准备 我们的服务器采用了 openEuler 22.03 (LTS-SP4) 的初始化服务器模式安装 二、安装步骤 (一)安装依赖库 在终端中运行以下命令确保系统安装了必要的依赖: sudo dnf install -y python3上述 Python 脚本中的依赖库会在运行 Py…...

【Python游戏开发】石头剪刀布游戏(附完整Python完整代码)
石头剪刀布游戏:Pygame实现 结果图前言核心函数思考步骤实现原理和公式代码实现结论结果图 前言 石头剪刀布是一种经典的猜拳游戏,简单易玩但却蕴含着一定的策略性。本文将介绍如何使用Python和Pygame库开发一个简单的石头剪刀布游戏,并探讨其中的核心功能实现和思考过程。 …...

ctfshow(94,95)--PHP特性--strpos函数
建议先学习intval函数相关内容 Web94 源代码: include("flag.php"); highlight_file(__FILE__); if(isset($_GET[num])){$num $_GET[num];if($num"4476"){die("no no no!");}if(preg_match("/[a-z]/i", $num)){die(&qu…...

C++ --- 多线程的使用
目录 一.什么是线程? 线程的特点: 线程的组成: 二.什么是进程? 进程的特点: 进程的组成: 三.线程与进程的关系: 四.C的Thread方法的使用: 1.创建线程: 2.join(…...

百度笔试(10.29)
判断s字符串是否可以通过添加某些字符变成t字符串,s是否是t的子序列 s input() n int(input()) st set() res [] for idx in range(n):t input()if t in st or len(t) < len(s):continuest.add(t)i,j 0,0while i < len(s) and j < len(t):if s[i] …...

数据库版本更新后,如何迁移数据?
数据库版本更新后迁移数据是一个需要谨慎处理的过程。以下是一般步骤和最佳实践: 1. **备份数据**: 在进行任何迁移之前,确保对现有数据库进行完整备份。这可以是物理备份(如数据库文件的拷贝)或逻辑备份ÿ…...

Chrome与火狐的安全功能全面评估
在当今数字化时代,网络安全已成为用户最为关注的问题之一。作为两款广受欢迎的浏览器,Chrome和火狐(Firefox)都提供了多种安全功能来保护用户的在线隐私和数据安全。本文将全面评估这两款浏览器的安全功能,帮助用户更好…...

微服务设计模式 - 重试模式(Retry Pattern)
微服务设计模式 - 重试模式(Retry Pattern) 定义 重试模式(Retry Pattern)是一种微服务中的设计模式,用于在临时性失败(如网络故障或暂时不可用的服务)发生时,自动重新尝试请求&…...

DNS配置
1.搭建dns服务器能够对自定义的正向或者反向域完成数据解析查询。 2.配置从DNS服务器,对主dns服务器进行数据备份。 正反向解析 [rootlocalhost ~]# vim /etc/named.conf options {listen-on port 53 { 192.168.111.130; };directory "/var/named&quo…...

【Linux指令】---获取进程的PID
获取进程的PID getpid()函数...

在centos中安装cmake
安装依赖工具: sudo yum install -y epel-release sudo yum groupinstall -y "Development Tools" sudo yum install -y wget wget 下载 CMake 3.24 的压缩包: wget https://github.com/Kitware/CMake/releases/download/v3.24.0/cmake-3.24.0-linux-x86_64.tar.gz …...

【补补漏洞吧 | 02】等保测评ZooKeeperElasticsearch未授权访问漏洞补漏方法
一、项目背景 客户新系统上线,因为行业网络安全要求,需要做等保测评, 通过第三方漏扫工具扫描系统,漏扫报告显示ZooKeeper和 Elasticsearch 服务各拥有一个漏洞,具体结果如下: 1、ZooKeeper 未授权访问【…...

Docker Compose一键部署Spring Boot + Vue项目
目录 前提条件 概述 Compose简介 Compose文件 Compose环境 Compose命令 帮助命令 关键命令 Compose部署项目 初始化环境 查看代码文件 sql数据准备 nginx配置文件准备 创建 compose.yaml 一键启动compose多个容器 浏览器访问虚拟机ip:80(可省略默认的80端口) …...

【maven】idea执行了maven的install命令给本地安装项目依赖包 安装后删除
目录 事件起因环境和工具操作过程解决办法1、找到对应的目录下的文件,手动去删除,比如我的依赖库的路径是D:\qc_code\apache-maven-3.8.2\repository 我只需要找到这个目录下对应的依赖包进行手动删除即可(不推荐,强行删除文件夹文…...

Android中的Handle底层原理
先上结论: 一个小小的Handle,就能看出你对Android底层源码理解的深浅。 老规矩,如果你能看懂我上面画的这张草图,这篇文章你可以直接跳过! 开干! 1、先对Handle在Android系统中是如何发挥作用的做一个小结: 1)每一个线程,包括UI主线程,有一个ThreadLocalMap变量:…...

最简单方式SSH连接局域网中另一台电脑的WSL2
1、首先确认一下WSL2中的SSH服务是否开启: 先安装更新一下,再安装一下ssh服务模块,这里很多人都每安装过。 sudo apt update sudo apt upgrade sudo apt install openssh-server 2、配置SSH服务器: 打开配置文件,这…...

热点聚焦:AI在医疗保健领域的深度渗透与变革
内容概要 随着人工智能技术的不断进步,我们正在见证一个充满奇迹的转变,尤其是在医疗保健领域。这种转变不仅仅涉及到提高效率,更在于重新定义我们对疾病诊断和治疗方案的理解。通过智能算法,AI能够在早期识别潜在的健康问题&…...

模板方法模式:定义算法框架的设计模式
1. 引言 在软件开发中,算法的实现通常是可变的,但其基本步骤往往是相对固定的。模板方法模式(Template Method Pattern)正是为了解决这一问题而设计的,它通过定义一个操作中的算法框架,将一些步骤的实现延…...

[Redis] Redis事务
🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…...

编译原理第一次实验报告
源代码及附件:编译原理实验一源程序及附件资源-CSDN文库实验题目 实验要求 实验设计 前两部分指出了实验的宏观把控,为了具体实施实验,我们需要预先为实验做出如下设计: 本次实验我选取了C语言的一个子集进行设计词法分析器&…...

uniapp的video视频属性打包app后层级过高
问题:在使用uniapp开发APP时,使用video标签显示视频发现H5可以正常展示,但是打包到APP后,它的层级过高,把底部导航都盖住了。 官网说明:uni-app官网 官网给了cover-view组件或plus.nativeObj.view、subNVue…...

问:Redis为什么这么快?
Redis,全称Remote Dictionary Server,是一个开源的高性能键值对数据库。它以其卓越的性能、丰富的数据结构和灵活的使用方式,在现代互联网应用中扮演着重要角色。本文将探讨Redis之所以快的原因,包括其数据结构、内存管理、IO多路…...

环信鸿蒙IM SDK实现附件消息发送与下载
环信HarmonyOS IM SDK 正式版已经发布,该版本全面覆盖即时通讯(IM)的核心功能,为用户提供了完整的IM全功能体验,同时支持从Android APK到 NEXT 的数据迁移,更好地满足企业在不同业务场景下的适配需求。 点…...

探索NetCat:网络流量监测与数据传输的利器
从简单的数据传输到复杂的网络调试,NetCat的灵活性和多功能性让人赞叹不已,在这篇文章中我将深入探讨NetCat的魅力,揭示它的基本功能、实用技巧以及在日常工作中的应用场景,发现如何用这一小工具提升的网络技能与效率。 目录 Net…...

【运动的&足球】足球运动员球守门员裁判检测系统源码&数据集全套:改进yolo11-DBBNCSPELAN
改进yolo11-FocalModulation等200全套创新点大全:足球运动员球守门员裁判检测系统源码&数据集全套 1.图片效果展示 项目来源 人工智能促进会 2024.10.28 注意:由于项目一直在更新迭代,上面“1.图片效果展示”和“2.视频效果展示…...

求最大公约数,最小公倍数
输入两个正整数 m 和 n,求其最大公约数和最小公倍数。 求最小公倍数算法: 最小公倍数 两整数的乘积 最大公约数 根据求最小公倍数的算法,可以看出如果已知最大公约数,就能很容易求出最小公倍数。而通过辗转相除法和相减法&#…...

Android——横屏竖屏
系统配置变更的处理机制 为了避免横竖屏切换时重新加载界面的情况,Android设计了一中配置变更机制,在指定的环境配置发生变更之时,无需重启活动页面,只需执行特定的变更行为。该机制的视线过程分为两步: 修改 Androi…...

scala---10.30
val、var package com_1030class Person {var name:String"rose"def sum(n1:Int,n2:Int):Int{n1n2} } object Person{def main(args: Array[String]): Unit {//创建person对象var personnew Person()println(person.sum(10,20))//30println(person.name)person.nam…...

Pinctrl子需要中client端使用pinctrl过程的驱动分析
往期内容 本专栏往期内容: Pinctrl子系统和其主要结构体引入Pinctrl子系统pinctrl_desc结构体进一步介绍Pinctrl子系统中client端设备树相关数据结构介绍和解析inctrl子系统中Pincontroller构造过程驱动分析:imx_pinctrl_soc_info结构体 input子系统专栏…...