文章目录

  • Pre
  • 分布式锁演进 V1
  • 分布式锁演进 V2
  • 分布式锁演进 V3
  • 分布式锁演进 V4
  • 分布式锁演进 V5
  • 终极版-分布式锁演进(Redisson ) V6
    • Code
    • Redisson分布式锁实现原理
    • 源码分析


Pre

Redis Version : 5.0.3

Redis进阶-核心数据结构进阶实战 中我们讲 strings 数据结构的时候,举了一个例子

事实上,要实现一把相对完善的分布式锁,需要注意的细节还是蛮多的,这里我们好好的梳理一把。


我们先来看段代码

 int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");}

redis中提前存储了一个key stock , value为 100

上述代码有问题吗?

是不是我们熟悉的超卖问题?

为啥会超卖? 假设同时有两个线程都执行到了 int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); , 比如都取到了stock为 100 , 然后继续执行后面的业务逻辑,到最后将扣减后的值set到redis中,应该剩98吧, 事实上呢? 你库存里的值是 99个… 卖到最后,是不是卖多了? 。。。。

那怎么办呢? 没有分布式经验的童鞋,可能会说 加把锁啊 云云

加锁后 变成了啥呢?

synchronized(this){int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");}
}

那 这样的代码还有问题吗?

  1. 性能问题
  2. 更为重要的是,如果你的应用是集群模式,好比 你有N个tomcat, 用户通过NG地址访问,你想想你的这个JVM级别的锁 ,还有啥用,一样会超卖…

这个时候你需要一把分布式锁,这里我们讨论的是如何使用redis实现分布式锁


分布式锁演进 V1

来, 上代码

         String key = "STOCK_LOCK";Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key,"ARTISAN_LOCK");if (!result){ // 如果未获取到锁,直接返回return "1001";}int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");System.out.println("扣减成功,剩余库存:" + realStock + "");}stringRedisTemplate.delete(key);return "扣减成功";

我们来分析下, stringRedisTemplate.opsForValue().setIfAbsent(key,"ARTISAN_LOCK"); 这行代码就保证了只有一个线程能set成功 (redis 的工作线程是单线程的嘛 ), setIfAbsent 不存在才设置,如果有一个线程设置成功了,在这个线程未释放之前,其他线程是无法set成功的,所以其他线程返回false,直接return了。


分布式锁演进 V2

那这个代码严谨吗? ---------> 有的同学说,你这个中间要是出异常了,没有执行 stringRedisTemplate.delete(key);,那岂不是这把锁释放不了了,死锁了呀? 要不try catch finally ?

那代码变成如下

那,这样就完美了吗? 抛出异常的场景我们是处理了,在finally里释放。


分布式锁演进 V3

那假设在运行的过程中,还没有执行到finally , 这个时候tomcat挂了,但是锁已经set到redis里了 咋办? --------》 有的同学说, 简单啊 加个超时时间呗。

那还有问题吗? ----------》如果宕机时间发生在

  Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key,"ARTISAN_LOCK");stringRedisTemplate.expire(key,5000,TimeUnit.SECONDS);

这两行代码之间,有怎么办? … 不会这么巧吧 …但理论上是存在的

继续聊


分布式锁演进 V4

本质上: 要把set key和 设置过期时间 搞成一个原子命令 .

低版本的Redis,你可能需要lua脚本,但是现在Redis提供了setnx 命令, spring也帮我们封装好了

最关键的一行代码


Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key,"ARTISAN_LOCK",10,TimeUnit.SECONDS);

代码就变成了

对于一般的应用,并发不是很高,这个也足够用了,因为简单啊

但是如果在高并发下,那还有问题吗? 这样就满足所有场景了吗 ?

我们在设置key的时候,给key设置的过期时间是 10秒 ,也就说 10秒后,这个key会被redis给删除掉, 假设你的这个业务执行了15秒才执行完。当前业务还未执行结束,第二个线程的请求已经过来了,它也能加锁成功。 第二个线程继续执行,执行了5秒,你的第一个线程也执行完了,最后一步 删除key , 那第一个线程就把第二个线程加的锁给删掉了啊。。。。。

删了别的线程加的锁,并发一高,你这个锁就没啥用了哇。。。所以 还有另外一个原则: 加锁和解锁必须是同一个线程 .


分布式锁演进 V5

加锁和解锁必须是同一个线程 . 实现的话也简单,value 不写死,写成一个线程ID或者随机数等等 都行,删除key的时候,比较下,相等的话才删除

根据V4存在的问题,我们来看下代码

那有的童鞋会问,如果 在finally 中 执行到if 挂了。。。并没有执行delete咋办? 理论上是有可能发生的, 其实也不要紧,我们set key的时候,设置了一个超时时间, 那最多锁10秒嘛 ,不会死锁。 也能接受。

如果你非得要想改这个地方,把查询和delete弄成一个原子命令,lua脚本就排上用场了。

这里我们不展开了。

到这里,一把相对完善的锁,就OK了。

关于到底设置多长的过期时间合适, 这个不好讲了, 1秒中是长是短 ,1分钟呢? 要权衡一下。 那有没有更好的办法呢?


终极版-分布式锁演进(Redisson ) V6

针对v5中存在的问题, 虽然解决了 加锁和解锁都是同一个线程, 但是还是有点小bug , 比如 你给key设置了过期时间为10秒, 但你的方法执行了15秒,方法还没执行完,锁已经被redis干掉了。。。另外一个线程就可以拿到锁,继续干活了。 多个线程同时执行,还是有潜在的bug出现。

超时的问题,你设置多长时间都不合适…

真的要彻底解决,咋弄呢? -------》 可不可以给锁续命? 没执行完就给锁延期呗。 说起来简单,实现起来有点复杂了。。。

简单来说,后台弄个定时任务,检测这个锁是否存在,存在的话延长时间,不存在的话就是被删掉了,不考虑即可。

好在Redisson提供了这个牛逼的功能。

Code

    @Beanpublic Redisson redisson() {// 此为单机模式Config config = new Config();config.useSingleServer().setAddress("redis://192.168.18.130:6379").setConnectionMinimumIdleSize(10).setDatabase(0);/*config.useClusterServers().addNodeAddress("redis://192.168.0.61:8001").addNodeAddress("redis://192.168.0.62:8002").addNodeAddress("redis://192.168.0.63:8003").addNodeAddress("redis://192.168.0.61:8004").addNodeAddress("redis://192.168.0.62:8005").addNodeAddress("redis://192.168.0.63:8006");*/return (Redisson) Redisson.create(config);}
   @RequestMapping("/deduct_stock")public String deductStock() throws InterruptedException {String lockKey = "STOCK_LOCK";// 获取锁RLock redissonLock = redisson.getLock(lockKey);try {// 加锁,实现锁续命功能redissonLock.lock();int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")if (stock > 0) {int realStock = stock - 1;stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)System.out.println("扣减成功,剩余库存:" + realStock + "");}}finally {// 释放锁redissonLock.unlock();}return "扣减成功";}

总结一下 三部曲

  1. 第一步:获取锁 RLock redissonLock = redisson.getLock(lockKey);
  2. 第二步: 加锁,实现锁续命功能 redissonLock.lock();
  3. 第三步:释放锁 redissonLock.unlock();

Redisson分布式锁实现原理


源码分析

Redis进阶- Redisson分布式锁实现原理及源码解析

Redis进阶-细说分布式锁相关推荐

  1. Redis进阶- Redisson分布式锁实现原理及源码解析

    文章目录 Pre 用法 Redisson分布式锁实现原理 Redisson分布式锁源码分析 redisson.getLock(lockKey) 的逻辑 redissonLock.lock()的逻辑 r ...

  2. Redlock——Redis集群分布式锁

    欢迎关注方志朋的博客,回复"666"获面试宝典 前言 分布式锁是一种非常有用的技术手段.实现高效的分布式锁有三个属性需要考虑: 安全属性:互斥,不管什么时候,只有一个客户端持有锁 ...

  3. redistemplate分布式锁实现_基于 Redis SETNX 实现分布式锁

    环境与配置 Redis 任意版本即可 SpringBoot 任意版本即可,但是需要依赖 spring-boot-starter-data-redis <dependency><gro ...

  4. 阿里JAVA面试题剖析:一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?...

    面试原题 一般实现分布式锁都有哪些方式?使用 redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? 面试官心理分析 其实一般问问题,都是这么问的,先 ...

  5. Redis 集群分布式锁与 API 网关分布式限流

    https://www.infoq.cn/article/FoQGIk*BzdQWJJ0tKqrJ Redis 集群的历史 Redis 在 3.0 前一般有两种集群方案,一是 proxy(Twempr ...

  6. 基于 Redis 实现的分布式锁

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:我的大学到研究生自学 Java 之路,过程艰辛,不放弃,保持热情,最终发现我是这样拿到大厂 offer 的! 作 ...

  7. redis系列:分布式锁

    1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

  8. Redis 如何实现分布式锁?

    锁是多线程编程中的一个重要概念,它是保证多线程并发时顺利执行的关键.我们通常所说的"锁"是指程序中的锁,也就是单机锁,例如 Java 中的 Lock 和 ReadWriteLock ...

  9. 小王,在 Java 中如何利用 redis 实现一个分布式锁服务呢???

    作者:杨高超 juejin.im/post/5a4984af6fb9a0450b66bc57 在现代的编程语言中,接触过多线程编程的程序员多多少少对锁有一定的了解.简单的说,多线程中的锁就是在多线程环 ...

最新文章

  1. python怎么安装matplotlib-python 安装matplotlib
  2. VIM进阶-宏Macro
  3. CentOS下的抓包服务:rpcapd(WinPcap)
  4. php 弹出变量,php取变量出现Notice: Undefined variable 的解决方法
  5. 《JavaScript高级程序设计(第四版)》红宝书学习笔记(1)
  6. 安卓python安装库_如何快速在安卓上搭建python3环境
  7. 为何我的苹果手机,每次打开软件都提示要登录ID帐号,而且软件打不开
  8. android audiorecord jni,Android AudioRecord初始化失败
  9. 数据驱动和关键字驱动
  10. 数据保密-第三代透明加密技术
  11. 利用js排序html表格
  12. Eclipse Community Survey 2012
  13. 如何在Vue项目中使用websql数据库
  14. python做图像识别好还是c++好_用于图像识别的顶级编程语言
  15. 印象笔记html预览,超级笔记使用指南 | 印象笔记
  16. 自己动手编程实现“电子地图下载器
  17. [转载]Windows系统的错误报告保存在哪个文件夹里?
  18. 数据结构-九宫格(回溯法)
  19. 1768 Problem A 算法7-15:迪杰斯特拉最短路径算法
  20. 仪表放大器 电流的检测

热门文章

  1. Flyme6系统适配教程(Patchrom)
  2. r语言向量代码如何创建函数c,R中向量基本操作:创建、运算、访问
  3. c语言课程设计怎么做,C语言课程设计————写下流程图! 谢谢
  4. win7驱动程序未经签名可以使用吗_如何解决高校机房计算机新CPU不支持win7系统的问题...
  5. liunx 上传 代码到github
  6. 您有新的订单提示音_《胡闹厨房:全都好吃》PS5新手柄专属功能细节揭露
  7. 机器学习中的矩阵向量求导(二) 矩阵向量求导之定义法
  8. ZooKeeper--一个具有高可用性的高性能协调服务
  9. Matplotlib实例教程(十一)堆栈图
  10. 找到真爱了-sublime