深入浅出 超详细 从 线程锁 到 redis 实现分布式锁(篇节 1)

深入浅出 超详细 从 线程锁 到 redis 实现分布式锁(篇节 1)在使用redis实现分布式锁之前我们需要先了解以下几点什么是分布式锁要介绍什么是分布式锁,那首先要提到与之对应的的两个锁:线程锁和进程锁1.线程锁主要用来给方法、代码块加锁。当某个方法或者代码块使用锁时,那么在同一时刻至多仅有一个线程可以执行该段代码。当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码。但是,其余线程是可以访问对象中没有被加锁的代码。线程锁只在同一个JVM中有效果,因为线程锁的实现在根

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

在 使用 redis 实现分布式锁 之前 我们需要先了解以下几点

什么是分布式锁

要介绍 什么是分布式锁,那首先要提到 与之对应的 的两个锁:线程锁 和 进程锁

1.线程锁

主要 用来 给方法、代码块加锁。当某个方法或者代码块使用锁时,那么在同一时刻至多仅有一个线程可以执行该段代码。当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码。但是,其余线程是可以访问对象中没有被加锁的代码。线程锁只在同一个JVM 中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如 Synchrogazeed、Lock 等。

2.进程锁

为了控制同一个操作系统中多个进程访问同一个共享资源,知识因为程序的独立性,各个进程是无法控制其他进程对资源的访问的,但是可以使用本地系统的信号量控制。

3.分布式锁

当多个进程不在同一个系统中时,使用分布式锁控制多个进程对资源的访问。

如何 实现分布式锁?

实现分布式锁的方法有很多,比如:使用redis、ZK 等

本文就以 redis 为例,一步步来实现 一把分布式锁。

redis 实现分布式锁

准备

建一个 springboot 项目,写一个前端控制器,控制器中 就是 简单模拟一个 减库存的逻辑,库存 放在 redis中。每次请求 都减一次库存。

@Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @GetMapping("/cut")
    public Object kc(){ 
   
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        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("扣减库存失败,库存不足");
        }
        return "ok";
    }

这里 我们先不使用 锁,看看 请求过来,是否会产生问题

先来玩玩 单应用 线程锁 吧

就先启动一个 实例,接下来进行测试

测试1:手动浏览器低频率 多次访问

在这里插入图片描述
貌似 很和谐呀? 库存也 正常减少,每次访问,就减少一个,很不错。

是不是开始怀疑了,为什么要用锁呢?还 搞什么 线程锁 进程锁 分布式锁,这不用锁不也正常减库存,不也没问题?

你是不是这样想的的呢?

咱们继续往下走 这才刚刚开始呢。。。

测试一 返回结果

测试2:模拟高并发 请求 扣减库存接口
  • 这里就使用 JMeter 来模拟 高并发 场景吧
  • 设置 库存 num = 1000
  • 发送 请求 1000 次
    在这里插入图片描述
  • 测试结果
    在这里插入图片描述
  • 剩余库存:518
    在这里插入图片描述
    按照我们的预想,每次请求 就减少一个库存,库存 总共 1000,发送了 1000 个请求,理论上 库存 应该为 0 啊,为什么剩余库存 还有 518 呢?
    再看上图,好多都是重复,一条记录代表一次请求,那么多次请求 打印出来的剩余库存 都是 剩余 999,这就是问题

我们简单分析一下,看图
-请求1进来,获取到 库存 为:1000,然后往下执行,进行本地扣减库存,当1号 本地扣减号库存后,准备将扣减完的剩余库存(999)保存到redis中时,此时还没来得及保存,2 号 就进来了 获取 库存(1000)虽然1号本地减了库存,但是它没有保存同步到redis中,所以redis中的 num 值 还是为 1000,此时 2 号进来 获取到的库存 就为 1000,依次类推,在 1 号 2 号 还没有将剩余库存同步到redis 中时,3号 4 号 5号 也进来了,获取库存值,还是为 1000,这时 大家都拿着 num = 1000 往下执行,导致 多次请求库存也只减少了1,我们就看到 那么多打印 相同 剩余库存。
在这里插入图片描述– 那之前 测试1 为什么 就没问题呢?

  • 那是因为请求频率低,等下次一请求进来,我上一次请求就已经执行完减库存 保存剩余库存的逻辑了,所以下一个进来就读到上一个扣减后的库存值,所以就没问题。
优化:加锁
使用 synchronized 实现 线程锁
@GetMapping("/cut")
    public Object kc() { 
   
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        synchronized (this) { 
   
            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("扣减库存失败,库存不足");
            }
        }
        return "ok";
    }
我们直接进行并发测试: 库存:1000,请求次数:1000 老样子

开头
在这里插入图片描述
结尾
在这里插入图片描述 一千条打印,太长了,我就 不全部贴出,就贴出 一头一尾,从头尾就可以很明显看出 与之前没有加锁的区别了吧。
每次请求减库存1,共请求了1000 次 刚好 剩余库存为:0
一次 不信,认为 巧合,可以多次试验一下,结果都是一样的(非分布式,仅此单体)

这下知道 锁 的作用了吧 hahahahah。。。。

要是 单体 架构,就一个项目 部署一个 Tomcat 那是没啥问题(其他性能什么的锁优化什么的这里忽略哈)

那 放在 分布式架构 多个 Tomcat 提供相同服务,那你这把 锁还能锁住吗?
开干,带你来测试一下就知道了

那就 来玩玩 多应用 线程锁 吧,看看是否锁得住

还是 上面的 项目,但我们启动两个 服务,同样 进行库存扣减

  • 对应端口为:
    • 服务1:ip:8888
    • 服务2:ip:9999

这里 使用到了 NGINX 做负载均衡,NGINX 的使用,下期给大家讲哟。

我这里测试环境 是 VM 虚拟机,centos7 ,在docker 中装了 一个 Nginx 为了方便开发测试。

其实 你用 本地 下载一个 NGINX ,然后开启动两次项目,提供两个服务,在NGINX上配置一下也是可以的。

在这里插入图片描述

OK 环境 准备就绪,那就开始 测试吧!

测试前 我们先预想一下结果吧,1000 次请求,每次扣减 1,那么刚好 请求解说库存就为:0。两个 系统同时进行扣减,每人扣减一个刚好扣减完。应该理想就差不多这样子吧。
那现实呢? 确实残酷的,看数据
  • 服务1:
    在这里插入图片描述
  • 服务2:
    在这里插入图片描述
    两个服务 出现了相同的数据,剩余库存都是:63,说明说明呢?

说明,库存 64 同时 被 两个服务获取到,这个就像前面我们讲的,多个线程同时 读到相同的库存数1000 一样,上一个线程扣减库存还没保存,就被下一个库存进来读取错误的 库存数,而 分布式呢,就是服务1 读到 库存为 64 然后进行执行本地扣减,在准备将扣减后的库存保存到 redis中时,原来这个另一个服务 就进行读取 库存操作,结果也读到 64,两个服务各自执行,结果出现两条 相同的数据“ 扣减库存成功,剩余库存为:63”。

那 有小伙伴就会问了,这不是 加锁了吗?怎么还会出错?
在这里插入图片描述

回顾 锁的概念

我们这里 使用 锁,是线程锁,只对 同一个jvm 有效,而分布式 多服务里,会有多个服务 部署在不同的 Tomcat 或服务器 上,所以 这个 synchronized 锁 不住别人的。
在这里插入图片描述

**

接下来 我们就 换一把大锁(分布式锁),看这把大锁能不能 锁住这些妖怪

**
在这里插入图片描述

请看 (篇节 2),我在 篇节 2 等你哟。。。

注释:内容 纯属 个人 见解 学习记录。如有 错误 望 大神 指教。。。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/152019.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 【转载文章】windows批处理命令教程_____附加自己学习笔记

    【转载文章】windows批处理命令教程_____附加自己学习笔记https://www.jb51.net/article/41322.htm批处理文件是无格式的文本文件,它包含一条或多条命令。它的文件扩展名为.bat或.cmd。在命令提示下键入批处理文件的名称,或者双击该批处理文件,系统就会调用Cmd.exe按照该文件中各个命令出现的顺序来逐个运行它们。使用批处理文件(也被称为批处理程序或脚本),可以简化日常或重复性任务。当然我们的…

    2022年8月22日
    6
  • pycharm配置python运行环境_C中调用Python

    pycharm配置python运行环境_C中调用Python目录前言一、系统路径说明二、环境配置1.在VS中选择ReleaseX642.菜单栏中选择:项目->属性3.生成dll答疑python程序目录下没有Numpy路径?具体代码参考博客前言本文主要讲解在Python程序中调用C(C++)程序的方法。主要思路是:在VS中配置好环境后,将C语言程序打包生成动态库文件.dll。将.dll改名成.pyd之后,装入相应路径,在pycharm中直接import即可环境:win10Pycharm(python3.6)(64位)VS2017(社区版)

    2022年8月26日
    6
  • css设置横向滚动条样式_js设置滚动条样式

    css设置横向滚动条样式_js设置滚动条样式接上一篇,有的时候在项目里面会使用到滚动条但是浏览器默认的滚动条的样式不怎么好看这个时候需要进行一些处理一般用到两种1:隐藏滚动条,但是可以支持滚动的方法::-webkit-scrollbar{display:none}示例:https://www.jianshu.com/p/9efdb18d92a62:自定义滚动条样式.healthName::-we…

    2025年7月21日
    3
  • 贪心算法及几个经典例子c语言_贪心算法一定是最优解吗

    贪心算法及几个经典例子c语言_贪心算法一定是最优解吗贪心算法一、基本概念:      所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。     贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前

    2025年8月24日
    2
  • win10下pycharm+Qtdesigner+Pyqt 成功配置

    win10下pycharm+Qtdesigner+Pyqt 成功配置请在安装之前,将整个博客看完之后再进行操作!我的安装过程有点曲折//使用Qtdesigner做可视化的界面设计,是一项很强大的工具。今天用到了,就在pycharm下配置一下,随手做点记录Qtdesigner可以可视化拖动生成界面。我的环境:win10pycharm+anaconda(python3.6.4)配置过程:1.在安装pyqt5的时候需要有sip的支持,sip…

    2022年8月25日
    9
  • 杂谈 – 自定义搜索引擎

    杂谈 – 自定义搜索引擎在Firefox上,以{肯定被驳回}搜索引擎为例。{肯定被驳回}搜索引擎搜索英文关键词时,给出的也基本是英文,例如这样:看了几篇不符合自己的胃口,遂点击了左上角小提示:仅限简体中文结果,例如这样:发现第二篇就是自己想要的东西,遂想,中文的结果也不错,何不设置不管中英文关键字,都先输出中文呢?所以实现方式,就是:添加addcustomsearchengine。2.点击扩展图标其中,SearchURL为:为了避免被驳回,请自行输入。然后点击蓝色按钮。然后在Firefox的

    2022年7月13日
    20

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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