深入浅出 超详细 从 线程锁 到 redis 实现分布式锁(篇节 2)「建议收藏」

深入浅出 超详细 从 线程锁 到 redis 实现分布式锁(篇节 2)「建议收藏」redis实现分布式锁上节我们讲了线程锁在单体项目中的作用,和放在分布式项目里产生的问题,那接下来我们就来解决分布式架构上怎么保证数据的一直性使用redisTemplate实现//设置锁setIfAbsent(“lock”,”1213″)—>SETNXlock”1213″//释放锁redisTemplate.delete(“lock”);@GetMapping(“/cut”)publicObjectkc(){

大家好,又见面了,我是你们的朋友全栈君。

redis 实现 分布式锁

上节 我们讲了 线程锁 在单体项目中的作用,和 放在 分布式 项目里产生的问题,那接下来我们就来解决 分布式 架构上怎么 保证 数据的一直性

使用 redisTemplate 实现

// 设置锁
setIfAbsent("lock", "1213")---> SETNX lock "1213"
// 释放锁
redisTemplate.delete("lock");
@GetMapping("/cut")
    public Object kc() { 
   
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        /** * 设置锁,如果 lock 不存在的话,设置 lock=1213 并返回 true * 如果存在的话:就不操作 直接返回 false */
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1213");
        if(!lock){ 
   
            // 锁存在
            return "服务繁忙,请稍后再试";
        }

        int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
        if (num > 0) { 
   
            int lastNum = num - 1;
            redisTemplate.opsForValue().set("num", lastNum + "");
            System.out.println("扣减库存成功,剩余库存为:" + lastNum);
        } else { 
   
            System.out.println("扣减库存失败,库存不足");
        }

        // 释放锁
        redisTemplate.delete("lock");

        return "ok";
    }

简述一下逻辑:
第一个请求 进来,设置一把锁 进行锁住,然后 往下执行减库存的操作,此时 第二个线程进来获取锁,但是第一请求没有释放锁,所以第二个请求获取锁就会失败 得到返回值为 false,进入if 方法体 直接返回了。其他线程也是如此,直到 第一个线程释放锁后 其他线程才有获取锁的机会,每次只有一个线程能够成功获取锁,其他线程获取不到直接返回。

大家 觉得 这把锁怎么样,是不是解决问题了呢?还有什么问题吗?有没有那个小可爱想到了呢?

肯定有小伙伴想到了。

情况1:一个请求进来,获取锁,执行扣减库存的操作,在它 释放锁之前 服务突然抛异常了呢?

  • 那就 使用 try{}finally{}嘛,这下不怕抛异常 无法释放锁了吧
try{ 
   
       int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
       if (num > 0) { 
   
           int lastNum = num - 1;
           redisTemplate.opsForValue().set("num", lastNum + "");
           System.out.println("扣减库存成功,剩余库存为:" + lastNum);
       } else { 
   
           System.out.println("扣减库存失败,库存不足");
       }

   }finally { 
   
       // 释放锁
       redisTemplate.delete("lock");
   }

情况2:要是不抛异常,在释放锁之前服务重启了呢?那就来不及执行finally了吧,这下 锁也没有释放吧。

那该怎么解决呢?

有小伙伴 就会想:给锁加一个 定时器嘛,它要挂就挂,到了时间 锁自动释放,其他人又可以获取到 锁 服务又可用了

感觉是可行哦,那我们来实际操作一下。源码如下:

在这里插入图片描述

OK 定时有了,要是 线程挂在里面,时间一到 锁就会自动释放

事实真的会 是这个样子的吗?

情况3:锁设置成功,接下来对 锁进行定时,此时准备定时呢,还没定时成功突然程序挂了,又会导致死锁,像前那情况一样。

那这时 我们就要保存设置锁 和 定时 两个指令的原子性,要么全部成功 要么全部失败,那该怎么实现呢?

这个问题 大牛已经都想过了,往下看

Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1213",10, TimeUnit.SECONDS);

一条更比两条强。这个底层使用 Lua 实现,保证其原子性。

再看一下 我们优化后的代码

@GetMapping("/cut")
    public Object kc2() { 
   
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        /** * 设置锁,如果 lock 不存在的话,设置 lock=1213 并返回 true * 如果存在的话:就不操作 直接返回 false */
        String lockKey = "lock";
        String redisClientId = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, redisClientId,10, TimeUnit.SECONDS);

        if(!lock){ 
   
            // 锁存在
            return "服务繁忙,请稍后再试";
        }
        try{ 
   
            int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
            if (num > 0) { 
   
                int lastNum = num - 1;
                redisTemplate.opsForValue().set("num", lastNum + "");
                System.out.println("扣减库存成功,剩余库存为:" + lastNum);
            } else { 
   
                System.out.println("扣减库存失败,库存不足");
            }
        }finally { 
   
            // 释放锁
            if (redisClientId.equals(redisTemplate.opsForValue().get(lockKey))){ 
   
                redisTemplate.delete(lockKey);
            }
        }
        return "ok";
    }

小伙伴 会说,这下没问题啦 哈哈。。。。

你确定 没问题?

那就我来给你分析分析

正常逻辑:一个请求进来 说去锁,设置有效时间为 10 秒,然后 执行下面 业务逻辑,最后释放锁,然后第二个线程进来。。。。

lock 有效时间为 10 秒,保不定 那个线程执行任务的时候 执行完要 15 秒,此时,lock 10秒就失效,那下一个线程就会进来,假如第二个线程要执行8 秒,第一个线程5秒后就执行完了 然后释放lock 锁,线程1的锁早就失效了,它释放的锁确实线程2的锁,而第二个线程还有3秒才执行完,此时线程3获取到锁,又进来了,3秒后线程2又释放线程3 的锁,这样下去线程3释放线程4.。。。 就会导致 锁永久失效。

所以 这个超时时间该设置多少呢? 就需要根据项目来考量了。

那有什么 好的解决方案吗,解决这个锁失效的问题呢?

有的,那肯定是有的,听我徐徐道来。。。

假设 设置锁有效时间为 30 秒,那当线程获取锁后,开一个子线程 做一个定时器,每隔一段时间去检查该对象的锁是否存在,存在的话 就重新给 锁续命。
那如何保证 自家的锁不会被别人释放呢?
这下那个 锁的 value 就派上用场了,给每个线程的锁配置上唯一标识(这个唯一标识就使用UUID)
每次释放锁的时候就判断是否是自己的锁,保证只释放自己的锁。

理论有了,那该怎么实现呢?

使用Redisson 实现分布式锁

1、引入 Redisson 依赖
<dependency>
   <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.0</version>
</dependency>
2、配置Redisson
@Bean
public Redisson redisson(){ 
   
    // 此为单机模式
    Config config = new Config();
    config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0).setPassword("root");
    return (Redisson) Redisson.create(config);
}
3、代码实现
@Autowired
private Redisson redisson;

@GetMapping("/cut")
public Object kc() { 
   
    String lockKey = "lock";
    RLock rLock = redisson.getLock(lockKey);
    try { 
   
        // 加锁
        rLock.lock();
        int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
        if (num > 0) { 
   
            int lastNum = num - 1;
            redisTemplate.opsForValue().set("num", lastNum + "");
            System.out.println("扣减库存成功,剩余库存为:" + lastNum);
        } else { 
   
            System.out.println("扣减库存失败,库存不足");
        }
    } finally { 
   
        // 释放锁
        rLock.unlock();
    }
    return "ok";
}
但是 主从架构 也是可能出现问题,如 redis 挂了
后期优化 可以 用 redis 集群,增加高可用。
OK redis 锁分布式就讲完了,到这里 锁就差不多了
要是还想更加完善 可以选择使用ZK来实现,但是性能是没有 redis 好。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/152027.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • pycharm汉化版安装_pokemmo语言汉化包

    pycharm汉化版安装_pokemmo语言汉化包一、在PyCharm主窗口中点击菜单栏中的File菜单下的Settings,进入PyCharm设置窗口,如下图所示二、在Setting中选择Plugins选项进入PyCharm插件管理窗口,在下图标注2处输入“chinese”系统会自动搜索相关插件,找到如下图标注3处所示的chinese(simplified)languagepackeap插件安装此插件(点击下图标注4处Install按钮)三、PyCharm中文语言插件安装完成后需要重启PyCharm,如下图所示。点击下图标注1或2处RE

  • 在毕设中学习02——numpy多维数组的切片,形态变化,维度交换

    在毕设中学习02——numpy多维数组的切片,形态变化,维度交换2022.5.21文章目录关于matplotlib.pyplotcv2工具python课本学习构建三维数组,并按照指定维度输出生成一组随机数,摆放为指定矩阵形式Python中range(start,stop,步长)生成指定范围,指定步长的一组数多维数组切片——过滤信息多维矩阵的维度顺序变换多维矩阵的切片多维矩阵的形态变化关于matplotlib.pyplotcv2工具两篇博客的学习文献学习python课本学习构建三维数组,并按照指定维度输出import numpy as np#a=np.

  • JS经常使用正則表達式【分享】

    JS经常使用正則表達式【分享】

  • LDC1314 学习资料

    LDC1314 学习资料一、模块概述1.电赛出现LDC1314芯片是2016年TI杯全国电子设计竞赛指定使用芯片,为了调试方便制作了模块如下图,它配合4个线圈可以检测磁性材料,比如硬币和铁丝等,2016比赛的题目就是小车检测铁丝。2.PCB图3.说明LDC1312-Q1和LDC1314-Q12和4通道,12位电感数字转换器(LDC)感应传感解决方案。多通道遥感支持的LDC1312-Q1和LDC131…

  • java中jbpm工作流_activiti工作流教程

    java中jbpm工作流_activiti工作流教程JBOSSjBPM 工作流管理系统简介jBPM,全称是JavaBusinessProcessManagement,是一种基于J2EE的轻量级工作流管理系统。jBPM是公开源代码项目,它使用要遵循 ApacheLicense。jBPM在2004年10月18日,发布了2.0版本,并在同一天加入了JBoss,成为了JBoss企业中间件平台的一个组成部分,它的名称也改成JBossj

  • 少儿编程的学习[通俗易懂]

    少儿编程第一课1.软件的认识2.顶部工具栏的认识3.认识背景,角色,舞台区,以及他们的分别上传4.代码库和代码编辑区第一课1.软件的认识Scratch是由MIT(美国麻省理工学院)针对5至16岁的儿童和青少年设计的可视化程序设计语言与开发环境,专注于用编程实现简单的动画效果。相比其他传统的编程语言,例如VB,Java,Pascal等相比,Scratch语言创建的目的不是为了培养少年程序员…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号