分布式锁Redision

news2024/10/7 18:23:18

目录

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 val
        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", "lock");
        if(flag){
            //获取锁成功,执行业务代码
            //1.先从redis中通过key num获取值  key提前手动设置 num 初始值:0
            String 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() {
        //设置uuid
        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));
            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://";

    /**
     * 自动装配
     */
    @Bean
    RedissonClient 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 {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * @param proceedingJoinPoint 能够获取请求之前的参数,请求的方法体,返回值等信息
     * @return
     */

    @Around("@annotation(com.atguigu.gmall.cache.GmallCache)")
    @SneakyThrows
    public 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();
//        组成缓存的key
        String 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.coloradmin.cn/o/415892.html

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

相关文章

数据结构:顺序表

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下数据结构方面有关顺序表的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C语言专栏&#xff1a;C语言&#xff1a;从入门…

阿里的Leader为什么牛逼?秘密都在“三板斧”里...

许多人都觉得&#xff0c;啊里出来的Leader&#xff0c;做事情都很有方法、有套路、有结果&#xff0c;秘密究竟在哪里&#xff1f;其实一个人的牛逼&#xff0c;首先是方法论的牛逼。本文就来聊聊&#xff0c;阿里Leader们都要学习的管理方法论&#xff0c;俗称阿里“三板斧”…

《MySQL系列-InnoDB引擎37》索引与算法-全文检索

全文检索 1 概述 对于B树的特点&#xff0c;可以通过索引字段的前缀进行查找。例如如下的查询方式是支持B树索引的,只要name字段添加了B树索引&#xff0c;就可以利用索引快速查找以XXX开头的名称。 select * from table where name like XXX%; 而如下这种情况不适合私有B索…

BUUCTF-sql注入联合查询的创建虚拟表-词频-steghide的使用

第七周第三次 目录 WEB [GXYCTF2019]BabySQli [GXYCTF2019]BabyUpload Crypto 世上无难事 old-fashion ​Misc 面具下的flag 九连环 WEB [GXYCTF2019]BabySQli 这是一道很新的题目 我们打开环境 发现登入注册界面 先看看源码有没有提示 发现有一个 php文件 进入…

Spark 对hadoopnamenode-log文件进行数据清洗并存入mysql数据库

一.查找需要清洗的文件 1.1查看hadoopnamenode-log文件位置 1.2 开启Hadoop集群和Hive元数据、Hive远程连接 具体如何开启可以看我之前的文章&#xff1a;(10条消息) SparkSQL-liunx系统Spark连接Hive_难以言喻wyy的博客-CSDN博客 1.3 将这个文件传入到hdfs中&#xff1a; hd…

OpenAI Translator | 基于ChatGPT API全局翻译润色解析及ORC上传图像翻译插件

简介 OpenAI Translator&#xff0c;一款基于 ChatGPT API 的划词翻译的浏览器插件和跨平台桌面端应用&#xff0c;使用 ChatGPT API 进行划词翻译和文本润色&#xff0c;借助了 ChatGPT 强大的翻译能力&#xff0c;帮助用户更流畅地阅读外语和编辑外语&#xff0c;允许跨 55 …

Qt音视频开发35-左右通道音量计算和音量不同范围值的转换

一、前言 视频文件一般会有两个声音通道及左右声道&#xff0c;值有时候一样有时候不一样&#xff0c;很多场景下我们需要对其分开计算不同的音量值&#xff0c;在QAudioFormat中可以获取具体有几个通道&#xff0c;如果是一个通道&#xff0c;则左右通道值设定一样&#xff0…

【时序数据库】时间序列数据和MongoDB第三部分-查询、分析和呈现时间序列数据...

在《时间序列数据和MongoDB:第1部分-简介》「时序数据库」时间序列数据与MongoDB&#xff1a;第一部分-简介中&#xff0c;我们回顾了理解数据库的查询访问模式需要询问的关键问题。在《时间序列数据和MongoDB:第2部分-模式设计最佳实践》「时序数据库」时序数据库和MongoDB第二…

Java实验课的学习笔记(二)类的简单使用

本文章就讲的是很基础的类的使用 重点大概就是类的构造函数以及一些很基础的东西。 实验内容是些老生常谈的东西&#xff0c;Complex类&#xff0c;在当初学C面向对象的时候也是这个样子展开的。 内容如以下&#xff1a; public class Complex {float real;float imag;public…

APK瘦身

先看下APK打包流程&#xff1a;APK打包流程_贺兰猪的博客-CSDN博客 知道了APK打包流程后想要瘦身&#xff0c;其实无非就是把整个APK的一些文件进行一个瘦身。 看下apk的这个文件。包括class、资源&#xff0c;资源生成arsc(资源映射表)&#xff0c;manifest清单&#xff0c;…

快排(非递归)及计数排序算法

都学了递归版的快速排序为何还要再学非递归实现&#xff1f;由于在递归过程中&#xff0c;如果数据量过大&#xff0c;那么实现时容易导致栈溢出&#xff0c;虽然代码没有问题&#xff0c;但是就是会崩&#xff0c;因此要将其改为非递归来实现 文章目录一、快速排序&#xff08…

如何使用Mac远程控制Windows电脑?

在你开始之前&#xff0c;设置您要远程处理的 Windows 计算机。 先安装 Microsoft Remote Desktop。 您可以在“应用程序”文件夹中检查它。 如果在个人计算机上安装&#xff0c;请转到 Apple App Store 并下载 Microsoft Remote Desktop。 如果在 TXST 计算机上安装&#xff0…

【C语言】递归解决经典题目(汉诺塔问题、青蛙跳台阶问题)

简单不先于复杂&#xff0c;而是在复杂之后。 目录 1. 汉诺塔问题 1.1 简介及思路 1.2 代码实现 2. 青蛙跳台阶问题 2.1 简介及思路 2.2 代码实现 1. 汉诺塔问题 1.1 简介及思路 汉诺塔问题是一种经典的递归问题&#xff0c;起源于印度传说中的塔 of Brahma。问题描…

手把手教你学习IEC104协议和编程实现 十 故障事件与复位进程

故障事件 目的 在IEC104普遍应用之前,据我了解多个协议,再综合自动化协议中,有这么一个概念叫“事故追忆”,意思是当变电站出现事故的时候,不但要记录事故的时间,还需记录事故前后模拟量的数据,从而能从一定程度上分析事故产生的原因,这个模拟量就是和今天讲解的故障…

silvaco 仿真BJT

本次实验为利用silvaco仿真BJT器件&#xff0c;分析不同p区厚度以及p区不同掺杂浓度研究其电流增益的变化。 一、器件要求 区域 掺杂方式 掺杂浓度或 峰值浓度&#xff08;/cm3&#xff09; 厚度&#xff08;um&#xff09; 宽度&#xff08;um&#xff09; N-漂移区 均匀…

微服务框架【笔记-Nacos注册中心】

接上篇&#xff0c;继续学习微服务框架中的Nacos注册中心。 Nacos注册中心 一、认识和安装Nacos 1.认识Nacos Nacos 是阿里巴巴的产品&#xff0c;现在是 SpringCloud 中的一个组件。相比 Eureka 功能更加丰富。 2.安装Nacos 下面给大家展示windows安装Nacos步骤&#xff1a;…

网络互联技术与实践教程(汪双硕、姚羽)——第四章 路由技术

第四章 路由技术 4.1 路由原理 路由是指通过相互连接的网络将数据从源地点转发到目标地点的过程。在路由过程中&#xff0c;数据通常会经过一个或多个中间节点&#xff0c;路由发生在网络层。路由包含两个主要的动作&#xff1a;确定最佳路径和通过网络传输信息&#xff0c;后…

刷题笔记【6】| 快速刷完67道剑指offer(Java版)

本文已收录于专栏&#x1f33b;《刷题笔记》文章目录前言&#x1f3a8; 1、包含min函数的栈题目描述思路&#xff08;双栈法&#xff09;&#x1f3a8; 2、栈的压入弹出序列题目描述思路&#xff08;辅助栈&#xff09;&#x1f3a8; 3、从上往下打印二叉树题目描述思路&#x…

chapter-4-数据库语句

以下课程来源于MOOC学习—原课程请见&#xff1a;数据库原理与应用 考研复习 概述 SQL发展 注&#xff1a;关键词是哪些功能&#xff0c;尤其第一个create alter drop是定义功能 1.SQL功能强大&#xff0c;实现了数据定义、数据操纵、数据控制等功能 2.SQL语言简洁&#xff…

【Java版oj】day26跳台阶扩展问题、快到碗里来

目录 一、跳台阶扩展问题 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 二、快到碗里来 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 一、跳台…