基于redis的分布式锁

news2024/11/25 0:48:27

一、redis分布式锁基本信息

1.详细讲解:

Redis 分布式锁是一种用于控制分布式系统中多个进程对共享资源的并发访问的机制。通过 Redis 的原子操作和过期时间功能,可以实现一个简单而有效的分布式锁。接下来,我们将详细介绍其工作原理、基本操作步骤以及代码实现,并提供详细的注释。

2.基本原理

2.1获取锁:

使用 SET 命令尝试设置一个键,并使用 NX 参数(如果键不存在则设置)和 PX 参数(设置键的过期时间)。
如果 SET 操作返回成功(即 OK),则表示获取锁成功。

2.2释放锁:

释放锁时需要确保只有持有锁的客户端才能释放锁,因此需要在释放锁时检查锁的值是否是当前客户端的标识。
使用 Lua 脚本来保证原子性:检查锁的值并删除锁。

二、代码示例

1.redis加锁以及解锁的工具类

1.1注意事项:

1.1.1 由于RedisTemplate交由spring管理,所以我们工具类也需要注册给spring,之后通过注入方式使用工具类,这样redisTemplate就不会是null;
1.1.2 用脚本的方式执行redisTemplate.execute会出现异常java.lang.UnsupportedOperationException: io.lettuce.core.output.ValueOutput does not support set(long),好像是返回值无法转换,由于redis版本不匹配,我这边redis版本是5.0.14.1;(暂未解决脚本执行后续会修改);

@Slf4j
@Component
public class LockUtil {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    //定义变量
    private final Object RELEASE_SUCCESS = 1;
    private final Object RELEASE_ERROR = 0;


    /**
     * 以阻塞方式的获取锁
     * @param key         key
     * @param value       value
     * @param lockTimeout 锁超时时间
     * @param getTimeout  获取锁超时时间
     * @return
     */
    public boolean lockBlock(String key, String value, long lockTimeout, long getTimeout, TimeUnit timeUnit) {
        long start = System.currentTimeMillis();
        //循环执行是否能加锁成功判断
        while (true) {
            //检测是否超时
            if (System.currentTimeMillis() - start > getTimeout) {
                log.error(Thread.currentThread().getName() + "get lock timeout");
                return false;
            }
            //执行set命令 ,如果返回 true,表示获取锁成功;如果返回 false,表示获取锁失败。
            Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value, lockTimeout, timeUnit);
            //是否成功获取锁
            if (absent != null && absent) {
                return true;
            } else {
                log.info(Thread.currentThread().getName() + "get lock fail:{},{}", key, value);
            }
        }
    }
    
    /**
     * 解锁:由于我这边无法执行redis脚本,一直返回异常,下面redis操作并非是原子性操作
     * @param key 加锁key
     * @param value 锁value
     * @return 返回是否解锁成功
     */
    public boolean unlock(String key, String value) {
        Object result = 0;
        String o = (String) redisTemplate.opsForValue().get(key);
        if (o != null && o.equals(value)) {
            Boolean delete = redisTemplate.delete(key);
            if (delete){
                result = 1;
            }
        }
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        log.error(Thread.currentThread().getName() + "unlock error");
        return false;

    }
}

2.调用实现

采用多线程方式模拟调用

@Slf4j
@RestController
public class TestController {
    @Resource
    private LockUtil lockUtil = new LockUtil();

    @GetMapping(value = "/redisLock")
    public void testRedis(@RequestParam(value = "key") String key) throws ExecutionException, InterruptedException {
        ExecutorService threadPoolTaskExecutor = Executors.newFixedThreadPool(10);
        CompletableFuture<Void> completableFuture1 = this.getCompletableFuture(threadPoolTaskExecutor, key);
        CompletableFuture<Void> completableFuture2 = this.getCompletableFuture(threadPoolTaskExecutor, key);
        CompletableFuture.allOf(completableFuture1, completableFuture2).get();
        threadPoolTaskExecutor.shutdown();
    }

    private CompletableFuture<Void> getCompletableFuture( ExecutorService threadPoolTaskExecutor,String key){
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            String value = UUID.randomUUID().toString();
            try {
                if (lockUtil.lockBlock(key, value, 3L, 10L, TimeUnit.SECONDS)) {
                    log.info(Thread.currentThread().getName() + "获取锁成功,value is {}", value);
                    Thread.sleep(2000);
                } else {
                    log.info(Thread.currentThread().getName() + "获取锁失败,value is {}", value);
                }
            } catch (InterruptedException e) {

                log.info(Thread.currentThread().getName() +"获取锁异常,value is {}", value);
            } finally {
                if (lockUtil.unlock(key, value)) {
                    log.info(Thread.currentThread().getName() + "释放锁,value is {}", value);
                }
            }
        }, threadPoolTaskExecutor);
        return  completableFuture;
    }
}

3.测试结果显示

运行结果

4.上面方法解决的问题

4.1通过对于redis key添加过期防止锁无法释放造成死锁;
4.2通过加锁时间限制防止加锁失败一直加锁,造成死锁;
4.3通过value比对保证解的锁是自己持有的;

5.上面方法存在的问题

5.1代码执行超过redis存放时间:

redis释放之后,别人可以获取锁这样锁就相当于失效,解决方案:看门狗机制,我们redisson已经解决上面问题了

5.2代码实现

1.redisson配置

@Configuration
@Slf4j
public class RedissonConfig {
    @Bean
    public RedissonClient createRedissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379");
                //.setPassword(""); // 如果没有密码,可以省略这一行

        return Redisson.create(config);
    }
}

2.代码调用实现

@RestController
public class TestController {

    @Resource
    private RedissonClient redissonClient;

    @GetMapping(value = "/redisLock")
    public void testRedis(@RequestParam(value = "key") String key) throws ExecutionException, InterruptedException {
        ExecutorService threadPoolTaskExecutor = Executors.newFixedThreadPool(10);
        CompletableFuture<Void> completableFuture1 = this.getCompletableFuture(threadPoolTaskExecutor, key);
        CompletableFuture<Void> completableFuture2 = this.getCompletableFuture(threadPoolTaskExecutor, key);
        CompletableFuture.allOf(completableFuture1, completableFuture2).get();
        threadPoolTaskExecutor.shutdown();
    }

    private CompletableFuture<Void> getCompletableFuture(ExecutorService threadPoolTaskExecutor, String key) {
        RLock lock = redissonClient.getLock(key);
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            try {
                // 尝试获取锁,等待时间10秒,上锁时间5秒
                if (lock.tryLock(10, 5, TimeUnit.SECONDS)) {
                    log.info(Thread.currentThread().getName() + "获取锁成功");
                    Thread.sleep(2000);
                } else {
                    log.info(Thread.currentThread().getName() + "获取锁失败");
                }
            } catch (InterruptedException e) {

                log.info(Thread.currentThread().getName() + "获取锁异常");
            } finally {
                lock.unlock();
                log.info(Thread.currentThread().getName() + "释放锁成功");
            }
        }, threadPoolTaskExecutor);
        return completableFuture;
    }
}

3.运行结果

在这里插入图片描述
不足之处,望海涵,希望大佬指点;

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

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

相关文章

杨氏矩阵和杨辉三角的空间复杂度较小的解题思路

文章目录 题目1 杨氏矩阵题目2 杨辉三角 题目1 杨氏矩阵 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 要求&#xff1a;时间复杂度小于O(N); 思路: 我们可以通过题目…

springboot SSM 宠物医院管理系统(源码+论文)

绪论 1.1 计算机管理信息系统的发展情况 1958 年美国首先提出了 “管理信息系统 ”&#xff08;即 MIS&#xff1a;Management Information Systems&#xff09;&#xff0c;从而也引出了另外一个概念 ——“管理信息系统 ”。综合其多种定义以及根据在实际中的应用&#xff…

C语言| 数组

直接定义一个数组&#xff0c;并给所有元素赋值。 数组的下标从0开始&#xff0c;下标又表示数组的长度。 【程序代码】 #include <stdio.h> int main(void) { int a[5] {1, 2, 3, 4, 5}; int i; for(i0; i<5; i) { printf("a[%d] %d\…

HSP_08章 断点调试

P100 断点调试 1. 基本介绍 一个实际场景 在开发中&#xff0c;新手程序员在查找错误时&#xff0c;这时有经验的程序员就会提示&#xff0c;可以用断点调试&#xff0c;一步一步的看源码执行的过程, 从而发现错误所在。 断点调试介绍 -基本介绍 2. 函数的调试

在不使用js在情况下只用css实现瀑布流效果

使用到的是grid 布局&#xff0c;需要注意的是grid-template-rows: masonry; 目前只有Firefox 浏览器支持这个效果&#xff0c;而且还是一个实验性属性需要在设置里面开发实验性选项才行。 实例 <!DOCTYPE html> <html> <head><title>Document</ti…

某国资集团数据治理落地,点燃高质量发展“数字引擎”

​某国有资产经营控股集团为快速提升集团的内控管理能力和业务经营能力&#xff0c;以数字化促进企业转型的信息化建设势在必行。集团携手亿信华辰开启数据治理项目&#xff0c;在数据方面成功解决“哪里来、怎么盘、怎么管、怎么用”的问题&#xff0c;不断推动企业数字化转型…

焦化行业排放平台简介

在当今社会&#xff0c;环保事业日益受到人们的关注。焦化行业作为重要的工业领域之一&#xff0c;其排放问题一直是环保工作的重点。为了有效控制焦化行业的排放&#xff0c;实施焦化行业排放平台成为了必不可少的措施。朗观视觉小编将详细探讨焦化行业排放平台的实施范围&…

vue技巧(十)全局配置使用(打包后可修改配置文件)

1、背景 vue打包目前主流用的有webpack和vite两种&#xff0c;默认用的webpack。&#xff08;二者的区别大家可以各自上网查&#xff0c;我没用过vite&#xff0c;所以不过多介绍&#xff09;vue通过webpack打包后&#xff0c;源码会被压缩&#xff0c;但一些关键配置可…

YOLOv10在RK3588上的测试(进行中...)

1.代码源 国内镜像站在gitcode。这个镜像站也基本上包含了github上常用项目的镜像。然后它的主发布源在这里&#xff1a; GitCode - 全球开发者的开源社区,开源代码托管平台 yolov10是清华主导做的... 然后&#xff0c;在维护列表里看到了这个&#xff1a; 2024年05月31日&am…

前端项目打包部署

打包 vue-cli脚手架的前端项目&#xff0c;点击npm脚本中的第二条编译命令&#xff0c;即可将项目编译&#xff0c;生成一个dist的文件夹&#xff0c;里面存放的就是编译好的前端项目文件&#xff0c;没有脚手架就在终端敲击npm run build命令编译前端项目 部署 Nginx 介绍:…

centos7 最简单方式安装xfce 4桌面环境

本快速指南将介绍如何在CentOS 7上安装Xfce桌面&#xff0c;该桌面将提供用于Linux系统的GUI。 Xfce是一个用于Unix之类的轻量级桌面环境&#xff0c;类似于操作系统&#xff0c;其目标是快速而又节省系统资源。 首先&#xff0c;我们需要配置EPEL存储库&#xff0c;因为这是…

Calibre版图验证工具调用_笔记

Siemens EDA Calibre版图验证工具调用 采用Cadence Virtuoso Layout Editor直接调用Siemens EDA Calibre工具需要进行文件设置&#xff0c; 在用户的根目录下&#xff0c;找到.cdsinit文件&#xff0c; 在文件的结尾处添加以下语句即可&#xff0c;其中&#xff0c;calibre.skl…

Kafka多维度调优

优化金字塔 应用程序层面 框架层面&#xff08;Broker层面&#xff09; JVM层面 操作系统层面 应用程序层面&#xff1a;应当优化业务代码合理使用kafka&#xff0c;合理规划主题&#xff0c;合理规划分区&#xff0c;合理设计数据结构&#xff1b; 框架层面&#xff1a;在不…

全面了解性格测试:探索你的内在世界

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 全面了解性格测试&#xff1a;探索你的内在世界 什么是性格测试&#xff1f; 定义和概念 &#x1f9e0; …

2006-2024年款别克君越维修手册和电路图资料更新

经过整理&#xff0c;2006-2024年款别克君越&#xff08;含君越混动版&#xff09;全系列已经更新至汽修帮手资料库内&#xff0c;覆盖市面上99%车型&#xff0c;包括维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针…

Oracle 打开钱包 ORA-28368: cannot auto-create wallet

ORA-28368: cannot auto-create wallet 开启钱包抱错&#xff0c;看下钱包信息 SQL> select * from v$encryption_wallet;WRL_TYPE -------------------- WRL_PARAMETER -------------------------------------------------------------------------------- STATUS ------…

直播录制怎么录?(3个方法)

在数字化快速发展的今天&#xff0c;直播已经成为了一种重要的传播方式&#xff0c;无论是商业活动、教育培训&#xff0c;还是娱乐休闲&#xff0c;直播都展现出了其独特的价值。然而&#xff0c;直播的即时性也意味着一旦错过&#xff0c;就很难再次体验。这时&#xff0c;直…

RTSP/Onvif安防视频监控系统EasyNVR录像播放时间轴与设备时间对应不上的原因

视频安防监控平台EasyNVR可支持设备通过RTSP/Onvif协议接入&#xff0c;并能对接入的视频流进行处理与多端分发&#xff0c;包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等多种格式。平台可提供视频实时监控直播、云端录像、云存储、录像检索与回看、告警等视频能力&#xf…

雷卯推出小封装高压防静电二极管SD60C

1. 高电压通讯信号线静电防护的为何有难度 高电压通讯信号线静电防护很多公司普遍可以做到1.8至36V,48V ESD静电防护&#xff0c;比如上海雷卯的36V ESD二极管SD36C&#xff0c;LC36CI&#xff0c;GBLC36C ,分别可以用于低数据速率到高数据速率静电防护。48V 的ESD二极管有SD4…

代码随想录算法训练营第二十二天| 235. 二叉搜索树的最近公共祖先、 701.二叉搜索树中的插入操作、 450.删除二叉搜索树中的节点

235. 二叉搜索树的最近公共祖先 题目链接&#xff1a;235. 二叉搜索树的最近公共祖先 文档讲解&#xff1a;代码随想录 状态&#xff1a;只会昨天的方法&#xff0c;没有想到利用二叉搜索树的性质。 思路&#xff1a; 如果当前节点的值同时小于 p 和 q 的值&#xff0c;说明 p …