图解 Redis 分布式锁,写得太好了!

news/2024/4/30 2:07:51/文章来源:https://blog.csdn.net/m0_71777195/article/details/127667561

分布式锁的演进

基本原理

我们可以同时去一个地方“占坑”,如果占到,就执行逻辑。否则就必须等待,直到释放锁。“占坑”可以去redis,可以去数据库,可以去任何大家都能访问的地方。等待可以自旋的方式。

阶段一

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {        //阶段一        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");        //获取到锁,执行业务        if (lock) {            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();            //删除锁,如果在此之前报错或宕机会造成死锁            stringRedisTemplate.delete("lock");            return categoriesDb;        }else {            //没获取到锁,等待100ms重试            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            return getCatalogJsonDbWithRedisLock();        }    } public Map<String, List<Catalog2Vo>> getCategoryMap() {        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();        String catalogJson = ops.get("catalogJson");        if (StringUtils.isEmpty(catalogJson)) {            System.out.println("缓存不命中,准备查询数据库。。。");            Map<String, List<Catalog2Vo>> categoriesDb= getCategoriesDb();            String toJSONString = JSON.toJSONString(categoriesDb);            ops.set("catalogJson", toJSONString);            return categoriesDb;        }        System.out.println("缓存命中。。。。");        Map<String, List<Catalog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});        return listMap;    }

问题: setnx占好了位,业务代码异常或者程序在页面过程中宕机。没有执行删除锁逻辑,这就造成了死锁

解决: 设置锁的自动过期,即使没有删除,会自动删除。

阶段二

 public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");        if (lock) {            //设置过期时间            stringRedisTemplate.expire("lock", 30, TimeUnit.SECONDS);            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();            stringRedisTemplate.delete("lock");            return categoriesDb;        }else {            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            return getCatalogJsonDbWithRedisLock();        }    }

问题: setnx设置好,正要去设置过期时间,宕机。又死锁了。

解决: 设置过期时间和占位必须是原子的。redis支持使用setnx ex命令。

阶段三

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {    //加锁的同时设置过期时间,二者是原子性操作    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111",5, TimeUnit.SECONDS);    if (lock) {        Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();        //模拟超长的业务执行时间        try {            Thread.sleep(6000);        } catch (InterruptedException e) {            e.printStackTrace();        }        stringRedisTemplate.delete("lock");        return categoriesDb;    }else {        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        return getCatalogJsonDbWithRedisLock();    }}

问题: 删除锁直接删除???如果由于业务时间很长,锁自己过期了,我们直接删除,有可能把别人正在持有的锁删除了。

解决: 占锁的时候,值指定为uuid,每个人匹配是自己的锁才删除。

阶段四

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {        String uuid = UUID.randomUUID().toString();        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();      //为当前锁设置唯一的uuid,只有当uuid相同时才会进行删除锁的操作        Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);        if (lock) {            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();            String lockValue = ops.get("lock");            if (lockValue.equals(uuid)) {                try {                    Thread.sleep(6000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                stringRedisTemplate.delete("lock");            }            return categoriesDb;        }else {            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            return getCatalogJsonDbWithRedisLock();        }    }

问题: 如果正好判断是当前值,正要删除锁的时候,锁已经过期,别人已经设置到了新的值。那么我们删除的是别人的锁。

解决: 删除锁必须保证原子性。使用redis+Lua脚本完成。

阶段五-最终形态

 public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {        String uuid = UUID.randomUUID().toString();        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();        Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);        if (lock) {            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();            String lockValue = ops.get("lock");            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +                    "    return redis.call(\"del\",KEYS[1])\n" +                    "else\n" +                    "    return 0\n" +                    "end";            stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), lockValue);            return categoriesDb;        }else {            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            return getCatalogJsonDbWithRedisLock();        }    }

保证加锁【占位+过期时间】和删除锁【判断+删除】的原子性。更难的事情,锁的自动续期。

Redisson

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。

其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)

Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

更多请参考官方文档:

https://github.com/redisson/redisson/wiki

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

上海各梯队IB学校怎么选?

近日&#xff0c;随着各大国际学校开始公布秋招信息&#xff0c;第一轮秋招考试也将在本周末正式到来。 除了春招主力军A-level学校以外&#xff0c;许多IB和AP美高学校的秋招都格外收到关注。上海到底有哪些优质的IB学校&#xff1f;学生的IB成绩和升学情况如何&#xff1f;什…

中国房车产业深度调研及未来发展现状趋势预测报告

高消费人群的房车旅行新宠&#xff0c;百亿规模产业正在爆发。 随着人们收入和消费水平的提高&#xff0c;具有移动性、独立性、私密性等特点的房车旅游正成为新的热门中高端旅游产品。在小红的书里&#xff0c;与房车相关的笔记有40多万条。在Tik Tok的“房车”和“房车旅行”…

日本知名汽车零部件公司巡礼系列之株式会社104

株式会社104 业务内容&#xff1a; 汽车部件制造(刹车零件、发动机支架、其他支架等) 房屋部件制造 复印机等零件制造 公司简介&#xff1a; 成立时间&#xff1a;1978年3月 资本金&#xff1a;1000万日元&#xff08;2022年汇率约50万人民币&#xff09; 员工数&#x…

BSA-PEI,牛血清白蛋白-聚乙烯亚胺,BSA-聚乙烯亚胺的保存

产品名称&#xff1a;牛血清白蛋白-聚乙烯亚胺&#xff0c;BSA-聚乙烯亚胺 英文名称&#xff1a;BSA-PEI 用途&#xff1a;科研 状态&#xff1a;固体/粉末/溶液 产品规格&#xff1a;1g/5g/10g 保存&#xff1a;冷藏 储藏条件&#xff1a;-20℃ 储存时间&#xff1a;1年 温馨提…

68、SpringAQMP(消息转化器)

SpringAQMP&#xff08;消息转化器&#xff09; 第一步&#xff1a;查看我们的发送消息感觉都可以是java对象 第二步&#xff1a;在配置里声明一个object队列 第三步&#xff1a;发送一个对象的消息 测试&#xff1a; RbMQ最早只支持字节&#xff0c;这里spring运行我们发obj…

JavaWeb传统商城(MVC三层架构)的促销功能模块【进阶版】

文章目录一.JavaWeb商城项目的促销功能模块【进阶版】开发过程记录1.1 项目背景1.2 需求分析1.3 开发流程/顺序二.促销页面(0.1颗星)2.1 需求介绍2.2 JSP页面2.3效果展示三,商品详情页面(0.2颗星)3.1 需求介绍和效果图3.2 数据库分析3.2 Servlet层3.3 Service层3.4 DAO层3.5 JS…

笔试强训(三十二)

目录一、选择题二、编程题2.1 淘宝网店2.1.1 题目2.1.2 题解2.2 斐波那契凤尾2.2.1 题目2.2.2 题解一、选择题 &#xff08;1&#xff09;处于运行状态的操作系统程序应放在(B) A.寄存器 B.主存 C.辅存 处于运行状态的操作系统程序也就是进程&#xff0c;进程需要放在内存中执…

Oracle行转列(pivot)和Oracle列转行(unpivot)

行变列&#xff0c;列变行在生成报表的时候经常遇到&#xff0c;行变列叫做"Pivot”, 反之叫做"Unpivot”。 在Oracle11g之前&#xff0c;一般都是通过case来实现&#xff0c;但是Oracle11g及其以后直接支持PIVOT和UNPIVOT的操作。 pivot 语法&#xff1a; SELECT *…

从零开始学习opencv——在虚拟环境下安装opencv环境

毕设准备做cv相关项目&#xff0c;今天开始学习cv基础知识&#xff0c;课程为B站“【不要再看那些过时的OpenCV老教程了】2022巨献&#xff0c;OpenCV零基础小白最新版全套教程(人工智能机器视觉教程)” 1.在windows系统中某文件夹下安装虚拟环境&#xff1a; pip install vir…

软件工程师进入编程世界的55个锦囊:《 好代码 ,坏代码》

软件工程领域关于如何写出优秀代码的建议和观点非常多。但生活没有那么简单, 绝不只是尽可能多地吸取好的建议并严格遵守。由于不同来源的建议往往相互矛盾&#xff0c;我们怎么知道要听从哪个建议。更重要的是&#xff0c;软件工程并不是一门精确的科学&#xff0c;不可能将其…

Spring Security是什么? - 简单例子(三)

2、spring security中&#xff0c;安全配置通过继承WebSecurityConfigurerAdapter来配置 Configuration public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter{protected void configure(HttpSecurity http) throws Exception {//做大量的配置/…

万字深剖 Linux I/O 原理

目录传统艺能&#x1f60e;梅开二度&#x1f914;当前路径&#x1f914;三大输入输出流&#x1f914;系统文件 I/O&#x1f914;open&#x1f60b;open 返回值&#x1f914;close&#x1f60b;write&#x1f60b;read&#x1f60b;文件描述符fd&#x1f60b;对应关系&#x1f6…

【好书推荐】《Python编程:从入门到实践(第2版)》

第二版是2020年底发布的&#xff0c;第二版相比较第一版更新了不少新东西。 不错的python入门书&#xff0c;第一部分讲基础知识&#xff0c;第二部分讲了三个实际的项目&#xff1a;一个小游戏&#xff0c;一个数据可视化程序&#xff0c;一个网站。 可以方便地下载全书的源…

学习笔记-Kioptrix4-WalkThrough

Kioptrix4-WalkThrough 文章作者 xidaner & r0fus0d 免责声明 本文档仅供学习和研究使用,请勿使用文中的技术源码用于非法用途,任何人造成的任何负面影响,与本人无关. 靶机地址 https://www.vulnhub.com/entry/kioptrix-level-13-4,25/ Description Again a long delay bet…

实验4 类与数组

实验任务51 #pragma once2 3 #include<iostream>4 #include<cassert>5 using std::cout;6 using std::endl;7 8 class vectorInt9 { 10 private: 11 /* data */ 12 int size; 13 int *p; 14 public: 15 vectorInt(int n); 16 vectorInt(int n,…

分布式光伏站远程监控组网解决方案

一、项目背景随着规模性的光伏电站陆续建设和投入运行&#xff0c;如何实时了解电站的运行状况&#xff0c;如何满足上一级系统或电网调度系统的监控需求成为了急需解决的事情。为使对分布式能源实现高效监控、满足电力接入电网要求、合理调配、集中监控、电网分析、配网自动化…

前端特效、js代码优化

1.旋转按钮边框 效果&#xff1a; 代码&#xff1a; <div class"container"><button class"btn">旋转边框</button></div>*{margin: 0;padding: 0;}.container{background: #000;height: 100vh;overflow: hidden;}.btn{/* borde…

35岁以后还能学软件测试吗?

之前看到一个问题“35岁学软件测试来得及吗”。 之前一直在工厂上班&#xff0c;看不到希望。 已经35岁了&#xff0c;想转学软件测试来得及吗&#xff1f; 经常会碰到类似这样的问题&#xff1a;担心自己学历不够&#xff0c;非计算机专业&#xff0c;害怕学不会&#xff0c;甚…

擎创技术流 | ClickHouse实用工具—ckman教程(1)部署安装

前言&#xff1a; 在数据量日益增长的当下&#xff0c;传统数据库的查询性能已满足不了业务需求。而Clickhouse在OLAP&#xff08;On-Line Analysis Processing——即一种在线分析处理的&#xff0c;用于数据分析的计算方法&#xff09;领域的应用&#xff0c;可以助力企业打造…