分布式锁实现原理

news2024/11/27 18:52:58

为什么需要分布式锁?

本地锁synchronized只能锁住当前服务进程,一个本地锁只能锁一个服务,如果是分布式服务情况下使用本地锁,那么多少服务就会有多少进程同时执行,就是去了锁的效果,为了到达分布式情况下一把锁能同时锁住全部服务,一次只放行一个进程那么就必须使用分布式锁
在这里插入图片描述
本地锁逻辑

/**
     * 本地锁
     */
    public Map<String, List<CategoryLevel2Vo>> getCatelogJsonFromDataWithLocalLock() {
        //Map<一级分类id,二级分类集合>

        //本地锁
        synchronized (this) {
            /**
             * 这种加锁,此项目只部署在一台服务器的情况下可以,本地锁锁住一个实例
             * 分布式情况下就不行,此项目部署在多台服务器上吗,每个锁锁住自己的实例只放一个线程进来
             * 如果8太服务器就会有8个线程同时访问,失去了锁的作用
             * 本地锁只能锁住当前进程,不能锁住其他服务
             *
             */
            //TODO 本地锁(当前进程锁)synchronized 、 (JUC)Lock  ,分布式情况下必须使用分布式锁
            //下一个线程拿到锁之后先查缓存,缓存中没有再查数据库,避免频繁查库
            String categoryJson = stringRedisTemplate.opsForValue().get("categoryJson");
            if (StringUtils.isNotBlank(categoryJson)){
                Map<String, List<CategoryLevel2Vo>> object = JSON.parseObject(categoryJson, new TypeReference<Map<String, List<CategoryLevel2Vo>>>() {});
                return object;
            }
            System.out.println("当前进程锁查询了数据库");
            List<CategoryEntity> categoryGetAll = baseMapper.selectList(null);
            //一级分类
            List<CategoryEntity> category1Level = getParentCid(categoryGetAll,0L);
            Map<String, List<CategoryLevel2Vo>> collect = category1Level.stream().collect(Collectors.toMap(item -> item.getCatId().toString()
                    , l1 -> {
                        //二级分类
                        List<CategoryEntity> category2Level = getParentCid(categoryGetAll,l1.getCatId());
                        List<CategoryLevel2Vo> collect2 = null;
                        if (category2Level != null){
                            collect2 = category2Level.stream().map(l2 -> {
                                //三级分类
                                List<CategoryEntity> category3Level = getParentCid(categoryGetAll,l2.getCatId());
                                List<CategoryLevel2Vo.CategoryLevel3Vo> collect3 = null;
                                if (category3Level != null){
                                    collect3 = category3Level.stream().map(level3 -> {
                                        return new CategoryLevel2Vo.CategoryLevel3Vo(l2.getCatId().toString(),level3.getCatId().toString(),level3.getName());
                                    }).collect(Collectors.toList());
                                }
                                return new CategoryLevel2Vo(l1.getCatId().toString(),collect3,l2.getCatId().toString(),l2.getName());
                            }).collect(Collectors.toList());
                        }
                        return collect2;

                    }));

            /**
             * 为什么将存入redis放在释放锁之前?
             * 线程拿到锁会去查缓存是否有数据,又因为我们向redis存入缓存数据是在释放锁之后
             * 那么释放锁之后,下一个线程查缓存,上一个线程并未存入完成。此时就会出现查询多次数据库的情况,锁失效
             * 故,存缓存数据应在锁释放之前完成
             */
            //存入缓存redis
            String s = JSON.toJSONString(collect);
            stringRedisTemplate.opsForValue().set("categoryJson",s,1, TimeUnit.DAYS);

            return collect;

        }

    }

分布式锁实现过程

在这里插入图片描述
基本原理:在redis中设置锁,执行完业务,释放锁操作,如果没有抢到锁就等待一段时间继续判断,自旋操作一定要返回

  1. 抢占锁
    向redis 存入一个锁 (“lock”,“xxx”),使用setNX命令,NX – 只有键key不存在的时候才会设置key的值
    stringRedisTemplate.opsForValue().setIfAbsent(“lock”, “1”);
    setIfAbsent()方法set成功返回true,所以我们只需要判断返回值为true就代表占锁成功

  2. 释放锁
    占锁成功后,执行完业务,然后删除这个key即可

    public Map<String, List<CategoryLevel2Vo>> getCatelogJsonFromDataWithRedisLock() {
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1");
        if(lock){
        	//抢占锁,set key 成功
        	//执行业务
        	Map<String, List<CategoryLevel2Vo>> data = getDataFromDb();
        	//业务....      
        	
        	//释放锁,删除key
        	stringRedisTemplate.delete("lock");
        	return data ;
        }else{
        	//自旋
        	return getCatelogJsonFromDataWithRedisLock();
        }
        
    }

问题1:如果执行业务时出现异常,那么将不会执行下面的释放锁操作,那么将会死锁
解决办法:set锁时指定过期时间,如果业务出现异常,一定时间后锁会自动释放锁
问题2:如果业务未执行完成,锁到了过期时间自动释放导致其他进程抢占锁,此时业务执行完成然后释放锁,此时就会释放其他线程的锁
解决办法:set锁时,value用使用uuid,这样每个进程就只能释放自己的锁,那么删除锁时就需要根据uuid判断是否是自己的锁再进行删除

优化逻辑

    public Map<String, List<CategoryLevel2Vo>> getCatelogJsonFromDataWithRedisLock() {
    	String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
        if(lock){
        	//抢占锁,set key 成功
        	//执行业务
        	Map<String, List<CategoryLevel2Vo>> data = getDataFromDb();
        	//业务....      
        	
        	//释放锁,判断uuid
        	String redisValue = stringRedisTemplate.opsForValue().get("lock");
        	if(uuid.equals(redisValue )){
        		stringRedisTemplate.delete("lock");
        	}
        	
        	stringRedisTemplate.delete("lock");
        	return data ;
        }else{
        	//自旋
        	return getCatelogJsonFromDataWithRedisLock();
        }
        
    }

问题3:如果释放锁操作时出现问题导致释放锁失败死锁怎么办?现在释放锁需要取值、判断、删除,如果操作出现问题就会死锁
解决办法:使用 Lua 脚本 执行释放锁的操作,因为脚本能保证原子性

Lua解锁脚本

if redis.call(“get”,KEYS[1]) == ARGV[1]
then
return redis.call(“del”,KEYS[1])
else
return 0
end

分布式锁最终优化逻辑
原子加锁(uuid Value+过期时间)+ 原子解锁(lua脚本) + 加长过期时间,且释放锁操作用finally块包起来以确保释放锁,如果没有拿到锁则sleep一段时间,防止频繁调用栈空间溢出
在这里插入图片描述

/**
     * 分布式锁 原子加锁(uuid Value+过期时间)+原子解锁(lua脚本)+加长过期时间
     */
    public Map<String, List<CategoryLevel2Vo>> getCatelogJsonFromDataWithRedisLock() {

        //set NX key不存在才能set
        //设置set lock hah EX 300 NX,设置过期时间EX,防止死锁,加锁跟设置过期时间必须是原子性操作
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
        //lock为true set成功
        if (lock){
            System.out.println("获取分布式锁成功");
            //加锁成功
            Map<String, List<CategoryLevel2Vo>> dataFromDb;
            try {
                dataFromDb = getDataFromDb();
            } finally {
                //释放锁(删除lock)
                //使用lua删除锁脚本,或者值+比较值+删除锁(脚本可以保证原子性)
                String redisScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
                Long execute = stringRedisTemplate.execute(new DefaultRedisScript<Long>(redisScript, Long.class), Arrays.asList("lock"), uuid);
            }
            return dataFromDb;
        }else {
            //加锁失败
            System.out.println("获取分布式锁失败,等待重试");
            //休眠
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //自旋
            return getCatelogJsonFromDataWithRedisLock();
        }

    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/607822.html

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

相关文章

3.9 流水作业调度问题

博主简介&#xff1a;一个爱打游戏的计算机专业学生博主主页&#xff1a; 夏驰和徐策所属专栏&#xff1a;算法设计与分析 1.我对流水调度问题的理解 流水作业调度问题是动态规划中的一个经典问题&#xff0c;它涉及将一系列作业分配给多个工作站以最小化总完成时间。该问题的…

go test 包外测试

之前文章有介绍过 go test coverage 单测覆盖率 和Go test基础用法&#xff0c;今天这里主要介绍 go 单测中比较特殊的一种场景&#xff1a;包外测试。初次看到这个名字&#xff0c;我还以为就是单独创建一个新目录&#xff0c;所有的单测用例统一都汇总到这个目录下&#xff0…

【P48】JMeter 断言持续时间(Duration Assertion)

文章目录 一、断言持续时间&#xff08;Duration Assertion&#xff09;参数说明二、测试计划设计 一、断言持续时间&#xff08;Duration Assertion&#xff09;参数说明 可以控制取样器的执行是否超过某个时间&#xff0c;如果超时则报错&#xff0c;持续时间断言器也叫超时…

21天学会C++:Day6----内联函数

CSDN的uu们&#xff0c;大家好。这里是C入门的第六讲。 座右铭&#xff1a;前路坎坷&#xff0c;披荆斩棘&#xff0c;扶摇直上。 博客主页&#xff1a; 姬如祎 收录专栏&#xff1a;C专题 目录 1. 知识引入 2. 知识点讲解 2.1 内联函数的使用 2.2 内联函数的特性 2.2 …

强大Excel 插件 Zbrainsoft Dose for Excel 3.6.2 Crack

强大的 Excel 插件 Zbrainsoft Dose for Excel 3.6.2 如果您厌倦了在Excel中消除重复的行&#xff0c;比较工作表或执行困难的活动&#xff0c;那么Dose for Excel是您需要的强大便捷解决方案&#xff0c;只需单击几下即可将所有这些复杂的杂务简化。它具有 100 多个强大的新功…

pytorch实战 -- 自动微分

autograd——自动求导系统 import torch torch.manual_seed(7) <torch._C.Generator at 0x7f3c1f9e0490> torch.autograd.backward(tensors, grad_tensorsNone, retain_graphNone, create_graphFalse) 功能&#xff1a;自动求取梯度 tensors&#xff1a;用于求导的张量&…

axios-CancelToken方法取消请求-控制多次同样api调用取消上一次接口调用

前言 开发当中看到了axios取消方法&#xff0c;经过查阅&#xff0c;axios这个包是提供了取消请求的方法的。 移动端当是tab栏类的页面&#xff0c;或者是下拉刷新和上拉加载是一个接口是&#xff0c;会出现20条情况&#xff08;接口调用2次&#xff09;。 pc端同一个按钮&am…

【欢迎您,xxx--JavaScript】

login.html <body><form action"index.html">用户名&#xff1a;<input type"text" name"usname"><input type"submit" value"登录"></form> </body> index.html <body><di…

BERT在GLUE数据集构建任务

0 Introduction 谷歌开源的BERT项目在Github上&#xff0c;视频讲解可以参考B站上的一个视频 1 GLUE部分基准数据集介绍 GLUE数据集官网GLUE数据集下载&#xff0c;建议下载运行这个download_glue_data.py文件进行数据集的下载&#xff0c;如果链接无法打开&#xff0c;运行…

七、Gitee码云的注册及使用(二)

1、创建远程仓库 (1)登录Gitee.com&#xff0c;点击右上角 号&#xff0c;再点击新建仓库。 (2)填写仓库名称&#xff0c;路径&#xff0c;仓库介绍 (3)选择是否开源 (4)初始化仓库 开源许可证&#xff1a;主要包括开源是否可以随意转载&#xff0c;开源但不能商业使用&…

yolov8改进大作战,开箱即用,提供yolov8魔术师专栏代码

1.yolov8魔术师专栏介绍 开箱即用&#xff1a;提供 yolov8魔术师专栏 代码&#xff0c;方便直接使用&#xff0c;无需自己重新添加引起的一些bug问题&#xff1a; https://blog.csdn.net/m0_63774211/category_12289773.html?spm1001.2014.3001.5482 专栏内容如下&#xff…

【深入浅出Spring Security(五)】自定义过滤器进行前后端登录认证

自定义过滤器 一、自定义过滤器自定义登录认证过滤器自定义 LoginFilter配置 LoginFilter测试 二、总结 一、自定义过滤器 在【深入浅出Spring Security&#xff08;二&#xff09;】Spring Security的实现原理 中小编阐述了默认加载的过滤器&#xff0c;里面有些过滤器有时并…

OpenGL实现第一个窗口-三角形

1.简介 此代码是基于QtOpenGL实现的&#xff0c;但是大部分的代码是OpenGL&#xff0c;Qt封装了一些类&#xff0c;方便使用。 2.准备工作 QOpenGLWidget提供了三个便捷的虚函数&#xff0c;可以重写&#xff0c;用来重写实现典型的OpenGL任务。不需要GLFW。 paintGL&#…

【C语言】Visual Studio社区版安装配置环境(保姆级图文)

目录 1. 官网下载社区版2. 选择安装项目2.1 点击使用C的桌面开发2.2 语言包选择简体中文2.3 设置安装位置 3. 创建新项目3.1 点击创建新项目3.2 点击空项目&#xff0c;下一步3.3 设置项目名称路径3.4 创建项目 4. 测试例程总结 欢迎关注 『C语言』 系列&#xff0c;持续更新中…

代码随想录 二叉树 Java (一)

文章目录 &#xff08;简单&#xff09;144. 二叉树的前序遍历&#xff08;简单&#xff09;94. 二叉树的中序遍历&#xff08;简单&#xff09;145. 二叉树的后序遍历二叉树的统一遍历方法&#xff08;参考代码随想录&#xff09;&#xff08;中等&#xff09;102. 二叉树的层…

横岗茂盛村旧改,已立项,一期已拆平。

项目位于龙岗区横岗街道红棉路与茂盛路交汇处&#xff0c;距离轨道3号线横岗站约700米。 茂盛片区城市更新单元规划&#xff08;草案&#xff09;已经在近日公示&#xff0c;该旧改被纳入《2012年深圳市城市更新单元计划第五批计划》&#xff0c;2019年曾被暂停&#xff0c;20…

Redis实战14-分布式锁基本原理和不同实现方式对比

在上一篇文章中&#xff0c;我们知道了&#xff0c;当在集群环境下&#xff0c;synchronized关键字实现的JVM级别锁会失效的。那么怎么解决这个问题呢&#xff1f;我们可以使用分布式锁来解决。本文咱们就来介绍分布式锁基本原理以及不同实现方式对比。 我们先来回顾&#xff…

【深度学习】混合精度训练与显存分析

混合精度训练与显存分析 ​ 关于参数精度的介绍可以见文章https://zhuanlan.zhihu.com/p/604338403 相关博客 【深度学习】混合精度训练与显存分析 【深度学习】【分布式训练】Collective通信操作及Pytorch示例 【自然语言处理】【大模型】大语言模型BLOOM推理工具测试 【自然语…

(论文阅读)Chain-of-Thought Prompting Elicits Reasoningin Large Language Models

论文地址 https://openreview.net/pdf?id_VjQlMeSB_J 摘要 我们探索如何生成一个思维链——一系列中间推理步骤——如何显著提高大型语言模型执行复杂推理的能力。 特别是&#xff0c;我们展示了这种推理能力如何通过一种称为思维链提示的简单方法自然地出现在足够大的语言模…

2023 更新版:苏生不惑开发过的那些原创工具和脚本

苏生不惑第431 篇原创文章&#xff0c;将本公众号设为星标&#xff0c;第一时间看最新文章。 4年来苏生不惑这个公众号已经写了400多篇原创文章&#xff0c;去年分享过文章更新版&#xff1a;整理下苏生不惑开发过的那些工具和脚本 &#xff0c;今年再更新下我开发过的原创工具…