分布式锁Redision

news/2024/9/13 11:56:32/

目录

1.ab工具(压测工具)的安装

2.前置

 3.优化

3.1synchronized修饰代码方法/代码块

3.2分布式锁事务的解决方案

3.3Redis实现锁问题

3.3.1 set ex方式

3.3.2 set ex方式+设置过期时间

3.3.3单redis结点的解决UUID和LUA脚本

3.3.4redission解决分布式锁

4.Redission解决分布式锁

4.1添加依赖

4.2相关配置类

4.3注解实现分布式锁


前置补充:

1.并发和事务区别:

并发的理解:Java 并发问题、产生的原因及解决方法 - 掘金 (juejin.cn)

在系统接受请求,先做并发处理,再事务处理。

每个人对资源的获取都相当于在一线程中,如果大量请求同时发生会导致磁盘资源的过度抢占,做不了别的事而导致宕机或变慢。然后在数据库的多表操作要考虑事务。

补充:修改mysql默认隔离级别

SELECT @@transaction_isolation; #8.0查看数据库事务
SET SESSION TRANSACTION ISOLATION LEVEL <isolation_level>; #设置事务隔离级别
isolation_level:
#读取未提交 READ UNCOMMITTED         
#允许读取已提交的数据  READ COMMITTED           
#可重复读        8.0默认数据库事务REPEATABLE READ   
#可串行化 最严格的SERIALIZABLE

2.事务的锁和并发的锁区别:

事务的锁,在事务内部进行,保障事务的原子性、一致性、隔离性、持久性。当事务提交或回滚就会释放。

并发的锁:防止cpu切换时候指令重排,保障多个并发操作同时进行数据的一致性完整性。并发加的锁在整个变更发操作期间都有效,直到手动释放或添加事务结束。

1.ab工具(压测工具)的安装

yum install -y httpd-tools

语法:  ab        n(一次发送的请求数量)        -c(请求的并发数)        访问路径

例子:5000个请求,100的并发 ps:注意关闭本地windows防火墙

ab  -n 5000 -c 100 http://192.168.200.1:8206/admin/product/test/testLock

set key value nx|xx ex|px

nx:表示不存在则进行操作

xx:存在再进行操作

ex:表示过期时间,秒

px:过期时间 毫秒

2.前置

提前再redis设置num=0;

ab  -n 5000 -c 100 http://192.168.34.93:8206/admin/product/test/testLock

 代码片段:

 3.优化

3.1synchronized修饰代码方法/代码块

此方法只有在单个服务的时候有效,如果分布式事务时,会导致失效

3.2分布式锁事务的解决方案

分布式锁的关键是:多线程共享内存标记(锁)

三种:

        1.基于数据库的分布式锁

        2.基于缓存(redis等)        性能最高

        3.基于zookeeper实现        最可靠

补充:数据库实现分布式锁过程(从性能上不考虑)

3.3Redis实现锁问题

        使用需注意点:

1.多线程可见: 多线程可见

2.死锁的情况:要保障锁的释放

3.排他:同一时刻,只能由一个进程获取锁

4.高可用:避免服务宕机(redis集群搭建:1.主从复制 2.哨兵3.cluster集群)

3.3.1 set ex方式

 通过 set ex key value 如果该字段不存在,判断为真则进行业务代码,如果存在则再次获取,直到获取锁为止。但是这种情况容易出现死锁的问题,如果发生异常,流程被打断了,就会发生死锁问题

    public void testLock() {//0.先尝试获取锁 setnx key valBoolean flag = redisTemplate.opsForValue().setIfAbsent("lock", "lock");if(flag){//获取锁成功,执行业务代码//1.先从redis中通过key num获取值  key提前手动设置 num 初始值:0String value = redisTemplate.opsForValue().get("num");//2.如果值为空则非法直接返回即可if (StringUtils.isBlank(value)) {return;}//3.对num值进行自增加一int num = Integer.parseInt(value);redisTemplate.opsForValue().set("num", String.valueOf(++num));//4.将锁释放redisTemplate.delete("lock");}else{try {Thread.sleep(100);this.testLock();} catch (InterruptedException e) {e.printStackTrace();}}}

3.3.2 set ex方式+设置过期时间

设置key的过期时间有两种方式

1.expire key timeout         此方法设置会在异常之后,永远不执行,

2.setex key timeout value ;开始就对lock设置过期时间                ps:set lock value ex 10 nx

情况:

1.设置锁的过期时间为3秒,但业务的执行实现为7秒。当锁的时间过期剩下4秒则有新的线程执行。

2.当业务执行完了,开始释放锁,但在他的前一秒刚有一个线程进入池子中,加锁进行操作。此时锁也被删掉了。没有拦住。

3.3.3单redis结点的解决UUID和LUA脚本

1.优化UUID防止误删:

  public void testLock() {//设置uuidString uuid = UUID.randomUUID().toString().replace("-","");Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3, TimeUnit.SECONDS);if (flag) {String value = this.redisTemplate.opsForValue().get("num");if (StringUtils.isEmpty(value)) {return;}int num = Integer.parseInt(value);this.redisTemplate.opsForValue().set("num", String.valueOf(++num));while (uuid.equals(this.redisTemplate.opsForValue().get("lock"))) {redisTemplate.delete("lock");}} else {try {Thread.sleep(10);this.testLock();} catch (InterruptedException e) {throw new RuntimeException(e);}}}

使用uuid防止误删锁后还有问题:

        1.还是会发生多个线程获取到资源(时间过期释放锁),实现锁的续期。

        守护线程---->expire key timeout; set key value ex timeout nx;

 代码:

            Thread thread = new Thread(() -> {this.redisTemplate.expire("lock", 3, TimeUnit.SECONDS);});thread.setDaemon(true);thread.start();

 2.优化LUA脚本保证删除的原子性

SET — Redis 命令参考

    public void testLock() {String uuid = UUID.randomUUID().toString().replace("-", "");Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);if (flag) {String value = this.redisTemplate.opsForValue().get("num");if (StringUtils.isEmpty(value)) {return;}int num = Integer.parseInt(value);this.redisTemplate.opsForValue().set("num", String.valueOf(++num));DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +"then\n" +"    return redis.call(\"del\",KEYS[1])\n" +"else\n" +"    return 0\n" +"end";redisScript.setScriptText(script);//设置响应类型redisScript.setResultType(Long.class);redisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);} else {try {Thread.sleep(10);this.testLock();} catch (InterruptedException e) {throw new RuntimeException(e);}}}

3.3.4redission解决分布式锁

以上方法适合redis的单结点适合解决,但如果redis搭建集群则不能解决

为什么?

        redis集群中,分片和结点之间的复制,使用lua脚本无法确保原子性操作,当使用Lua脚本时候,他将在一个结点上执行,而数据可能被分配在多个结点,在执行脚本的期间,节点通信不一致。所以lua脚本无法在集群中锁住资源

解决方案:

        redisson-redLock:大部分结点加锁成功,我就判断加锁成功[过半机制]

为了满足分布式锁可用:

1.互斥性:任意时刻,只能由一个客户持有锁

2.不会发生死锁,即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保障其他用户加锁

3.加锁和解锁必须同一个用户

4.加锁和解锁必须有原子性

4.Redission解决分布式锁

Redisson是一个在Redis的基础上实现的Java驻内存数据网格,提供多种数据类型,促进使用者对Redis的关注分离。

官网地址:Home · redisson/redisson Wiki · GitHub

github:GitHub - redisson/redisson: Redisson - Easy Redis Java client with features of In-Memory Data Grid. Over 50 Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Publish / Subscribe, Bloom filter, Spring Cache, Tomcat, Scheduler, JCache API, Hibernate, MyBatis, RPC, local cache ...

4.1添加依赖

<!-- Redisson -->
<dependency><groupId>org.Redisson</groupId><artifactId>Redisson</artifactId><version>3.15.3</version>
</dependency>

4.2相关配置类

@Data
@Configuration
@ConfigurationProperties("spring.redis")
public class RedissonConfig {private String host;private String addresses;private String password;private String port;private int timeout = 3000;private int connectionPoolSize = 64;private int connectionMinimumIdleSize = 10;private int pingConnectionInterval = 60000;private static String ADDRESS_PREFIX = "redis://";/*** 自动装配*/@BeanRedissonClient redissonSingle() {Config config = new Config();if (StringUtils.isEmpty(host)) {throw new RuntimeException("host is  empty");}SingleServerConfig serverConfig = config.useSingleServer()//redis://127.0.0.1:7181.setAddress(ADDRESS_PREFIX + this.host + ":" + port).setTimeout(this.timeout).setPingConnectionInterval(pingConnectionInterval).setConnectionPoolSize(this.connectionPoolSize).setConnectionMinimumIdleSize(this.connectionMinimumIdleSize);if (!StringUtils.isEmpty(this.password)) {serverConfig.setPassword(this.password);}config.useClusterServers().addNodeAddress().addNodeAddress();/*集群版:config.useClusterServers().addNodeAddress().addNodeAddress();*/// RedissonClient redisson = Redisson.create(config);return Redisson.create(config);}
}

4.3注解实现分布式锁

Core Technologies

添加注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GmallCache {/** 缓存数据前缀* */String prefex() default "cache:";/*缓存数据后缀*/String suffix() default ":info";
}

使用aop的方式环向切入

@Component
@Aspect
public class GmallCacheAspect {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate RedissonClient redissonClient;/*** @param proceedingJoinPoint 能够获取请求之前的参数,请求的方法体,返回值等信息* @return*/@Around("@annotation(com.atguigu.gmall.cache.GmallCache)")@SneakyThrowspublic Object cacheAspect(ProceedingJoinPoint proceedingJoinPoint) {
//        声明一个对象Object obj = new Object();/** 1.实现分布式锁的逻辑* 获取到缓存的key;注解前缀+参数+注解后缀* 获取到方法签名* *///获取签名MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();GmallCache annotation = signature.getMethod().getAnnotation(GmallCache.class);//获取参数Object[] args = proceedingJoinPoint.getArgs();//获取前缀String prefex = annotation.prefex();//获取后缀String suffix = annotation.suffix();
//        组成缓存的keyString skuKey = prefex + Arrays.asList(args) + suffix;try {obj = this.redisTemplate.opsForValue().get(skuKey);if (obj == null) {
//                查询数据库,加一把锁String lockKey = prefex + ":lock";RLock lock = this.redissonClient.getLock(lockKey);lock.lock();try {
//                    查询数据库obj = proceedingJoinPoint.proceed(args);if (obj == null) {Object o = new Object();//                    放入缓存this.redisTemplate.opsForValue().set(skuKey, o, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);return o;}this.redisTemplate.opsForValue().set(skuKey, obj, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);return obj;} catch (Throwable e) {e.printStackTrace();} finally {lock.unlock();}} else {return obj;}} catch (RuntimeException e) {throw new RuntimeException(e);}return proceedingJoinPoint.proceed(args);}
}


http://www.ppmy.cn/news/47986.html

相关文章

实验4 Matplotlib数据可视化

1. 实验目的 ①掌握Matplotlib绘图基础&#xff1b; ②运用Matplotlib&#xff0c;实现数据集的可视化&#xff1b; ③运用Pandas访问csv数据集。 2. 实验内容 ①绘制散点图、直方图和折线图&#xff0c;对数据进行可视化&#xff1b; ②下载波士顿数房价据集&#xff0c;并…

(一)Linux 环境下搭建 ElasticSearch (CentOS 7)

目录 1、搭建 Linux 相关环境 2、执行解压操作 3、创建新用户 4、修改配置文件 elasticsearch.yml 5、启动 ElasticSearch 6、修改虚拟机配置文件 7、重新启动 ElasticSearch 8、查看是否启动命令 9、访问 ElasticSearch 1、搭建 Linux 相关环境 没有服务器安装VM&a…

基于遗传算法的中药药对挖掘系统的设计与实现

用数据挖掘技术研究了中药方剂配伍的规律。主要工作&#xff1a;分析了关联规则存在的问题&#xff0c;引入双向关联规则的概念&#xff1b;介绍了遗传算法的基本原理&#xff0c;研究了遗传算法在数据挖掘中的应用&#xff1b;将方剂库转换为位图矩阵&#xff0c;大大提高搜索…

SpringBoot的Interceptor拦截器的简介和实际使用

拦截器&#xff08;Interceptor&#xff09; 概念&#xff1a;是一种动态拦截方法调用的机制&#xff0c;类似于过滤器。Spring框架中提供的&#xff0c;用来动态拦截控制器方法的执行。 作用&#xff1a;拦截请求&#xff0c;在指定的方法调用前后&#xff0c;根据业务需要执行…

windows和linux上证书的增删查

文章目录 引言windows上对个人证书的增删查创建证书证书的查找证书的删除证书的安装 Linux上对个人证书的增删查创建证书证书的安装证书的查看证书的删除 Linux上对系统证书的增删查 引言 PS: 我之前看过《图解密码技术》&#xff0c;已经对证书这些概念有基本的了解&#xff…

Java中的数学相关类

文章目录 1.java.lang.Math2.java.math包2.1 BigInteger2.2 BigDecimal2.3 java.util.Random 1.java.lang.Math java.lang.Math 类包含用于执行基本数学运算的方法&#xff0c;如初等指数、对数、平方根和三角函数。类似这样的工具类&#xff0c;其所有方法均为静态方法&#…

Baumer工业相机堡盟工业相机如何联合BGAPI SDK和OpenCVSharp实现Mono12和Mono16位深度的图像保存(C#)

Baumer工业相机堡盟工业相机如何联合BGAPI SDK和OpenCVSharp实现Mono12和Mono16位深度的图像保存&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机保存位深度12/16位图像的技术背景代码案例分享1&#xff1a;引用合适的类文件2&#xff1a;BGAPI SDK在图像回调中联合O…

4个实用JS库99%的人可能都不知道

前言 作为一名前端开发者&#xff0c;我通过这些JavaScript库大大提高了自己的效率&#xff0c;比如格式化日期、处理URL参数、调试手机网页等。因此&#xff0c;我想将这些好用的库分享给你们&#xff0c;也希望可以帮助到你们。 1.使用“Day.js”格式化日期和时间 地址&am…

【论文写作】如何写科技论文?万能模板!!!(以IEEE会议论文为例)

0. 写在前面 常言道&#xff0c;科技论文犹如“八股文”&#xff0c;有固定的写作模式。本篇博客主要是针对工程方面的论文的结构以及写作链条的一些整理&#xff0c;并不是为了提高或者润色一篇论文的表达。基本上所有的论文&#xff0c;都需要先构思好一些点子&#xff0c;有…

自定义泛型,自定义泛型接口,自定义泛型方法,JUnit,

class 类名<T,R....>{成员}//...表示可以有多个泛型因义的为静态是和类相关的&#xff0c;在类加载时&#xff0c;对象还没有创建&#xff0c; 泛型是对象创建的时候定 所以&#xff0c;如果静态方法和静态属性使用了泛型&#xff0c;JVM就无法完成初始化注意事项 packag…

paddlepaddle 的 CPU 和 GPU

想记录一下一个 bug 改了一上午改到最后发现并没有 bug 的 bug。 总结&#xff1a; 因为下午要跑很久&#xff0c;为了省 GPU 算力&#xff0c;我想上午先用 CPU 把数据处理部分跑出来&#xff08;感觉数据处理部分不像网络训练那样涉及太多计算&#xff0c;所以感觉用 CPU 就…

【定制功能】LVGL 邮件日志功能

更多源码分析请访问:LVGL 源码分析大全 目录 1、基本说明2、配置方法3、APIs3.1、xs_send_email_log1、基本说明 邮件日志功能是为了方便定位客户问题的方案。在使用此功能时,需要保证网络连接是正常的。 内存使用 日志功能使用的内存不超过 9K: 数据缓存(4096) + 消息缓存…

JDBC入门数据库连接

1. JDBC入门 JDBC&#xff08;Java Database Connectivity&#xff09;是Java程序与数据库进行交互的一种标准接口&#xff0c;它提供了一种简单的方式来连接和操作数据库。在使用JDBC之前&#xff0c;需要先了解以下几个概念&#xff1a; JDBC Driver&#xff1a;JDBC驱动程…

D. Marcin and Training Camp(思维 + 判断一个数二进制位是否是另一个数的子集)

Problem - D - Codeforces 马辛是他大学里的一名教练。有N个学生想参加训练营。马辛是个聪明的教练&#xff0c;所以他只想派那些能冷静合作的学生参加。 让我们关注一下这些学生。每个学生可以用两个整数ai和bi来描述&#xff1b;bi等于第i个学生的技能水平&#xff08;越高越…

十二、详解Kubernetes存储卷的技术原理

Kubernetes存储卷是Kubernetes中用于持久化存储数据的一种抽象概念。它们允许容器在不同的Pod之间共享数据,并且可以在Pod重新调度或迁移时保留数据。本文将详细介绍Kubernetes存储卷的原理。 1.存储卷的概念 Kubernetes存储卷是为了解决容器化环境下数据持久化的问题而引入…

linux_FIFO命名管道-mkfifo函数-进程通信

接上一篇&#xff1a;linux_管道学习-pipe函数-管道的读写-fpathconf函数 本次来分享FIFO命名管道&#xff0c;一些常识&#xff0c;开始上菜&#xff1a; 1.FIFO-mkfifo函数 FIFO常被称为命名管道&#xff0c;以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间。…

第三章 法的渊源与法的分类

目录 第一节 法的渊源的分类 一、法的渊源释义二、法的渊源种类 第二节 正式法源 一、正式法源的含义二、当代中国的正式法源三、正式法源的一般效力原则 第三节 非正式法源 一、当代中国的非正式法源 第四节 法的分类 一、法的一般分类二、法的特殊分类 第一节 法的渊源的…

Spring AOP核心概念与操作示例

AOP 核心概念 还记得我们Spring有两个核心的概念嘛&#xff1f;一个是IOC/DI&#xff0c;另一个是AOP咯。 先来认识两个概念&#xff1a; AOP(Aspect Oriented Programming)面向切面编程&#xff1b;作用&#xff1a;在不惊动原始设计的基础上为其进行功能增强&#xff0c;类…

Spring Security 05 密码加密

目录 DelegatingPasswordEncoder 使用 PasswordEncoder 密码加密实战 密码自动升级 实际密码比较是由PasswordEncoder完成的&#xff0c;因此只需要使用PasswordEncoder 不同实现就可以实现不同方式加密。 public interface PasswordEncoder {// 进行明文加密String encod…

AI大模型加速RPAxAI时代到来,谁会是RPA领域的杀手级应用?

GPT等AI大模型震撼来袭&#xff0c;基于RPA的超级自动化仍是最佳落地载体 对话弘玑CPO贾岿&#xff0c;深入了解国产RPA厂商对AI大模型的探索与实践 文/王吉伟 关于RPA已死的说法&#xff0c;在中国RPA元年&#xff08;2019年&#xff09;投资机构疯狂抢项目之时就已经有了。…