前言

上一篇老猫和小伙伴们分享了为什么要使用分布式锁以及分布式锁的实现思路原理,目前我们主要采用第三方的组件作为分布式锁的工具。上一篇运用了Mysql中的select ...for update实现了分布式锁,但是我们说这种实现方式并不常用,因为当大并发量的时候,会给数据库带来比较大的压力。当然也有小伙伴给老猫留言说“ 在quartz的集群模式中,就是使用了基于mysql的分布式锁,select for update ”。没错,其实quartz的集群模式中,任务执行的节点个数是可预知的,而且没有那么大的量级,所以是没有问题的。但是如果像千万级别的并发秒杀场景的情况下,那么这种方案其实是不可行的。因为mysql操作是需要IO的,IO的速度比内存速度慢,因此mysql如果在那种场景下使用的话是会存在系统瓶颈的。所以本篇就和小伙伴们分享基于内存操作的比较常用的分布式锁——redis分布式锁。

手撸Redis分布式锁

实现原理

redis分布式锁实现原理其实也是比较简单的,主要是依赖于redis的 set nx命令,我们来看一下完整的设置redis的命令:“Set resource_name my_random_value NX PX 30000”。看到这串命令,了解redis的小伙伴应该都看得懂这条命令是在redis中存入一个带有过期时间的值。具体上述设值语句解释如下:

  1. resource_name:资源名称,可以根据不同的业务区分不同的锁。(其实就是对应我们上一篇myql锁中的business_code)。
  2. my_random_value:随机值,每个线程的随机值都不相同,主要用于释放锁的时候用来校验。
  3. NX:key不存在的时候设置成功,key存在则设置不成功。
  4. PX:自动失效时间,如果出现异常情况,锁可以过期实现,因此达到了自动释放。

那么为什么可以使用这个思路呢?其实很简单,主要就是利用了set nx的原子性,在多个线程并发执行时,只有一个线程可以设置成功,如果设置成功,那么就代表着获得了锁,就可以执行后续的业务。如果出现了异常,过了锁的有效期,锁会自动释放,释放锁主要采用了redis的delete命令,释放锁之前会校验当前redis存储的随机数,只有当前的随机数和存储的随机数一致的时候才允许释放。具体的redis的删除,我们可以通过lua脚本进行删除,具体Lua脚本如下:

  1. if redis.call("get",KEYS[1]) == ARGV[1] then
  2. return redis.call("del",KEYS[1])
  3. else
  4. return 0
  5. end

那么我们为什么要采用这种方式释放锁呢?其实使用这种方式释放锁可以避免删除别的客户端获取成功的锁 。

如下图:

客户端A取得资源锁,但是紧接着被一个其他操作阻塞了,当客户端A运行完毕其他操作后要释放锁时,原来的锁早已超时并且被Redis自动释放,并且在这期间资源锁又被客户端B再次获取到。如果仅使用DEL命令将key删除,那么这种情况就会把客户端B的锁给删除掉。使用Lua脚本就不会存在这种情况,因为脚本仅会删除value等于客户端A的value的key(value相当于客户端的一个签名)(说明:其实这些例子在redis的官网都有介绍)。

代码实现方式

老猫对redis锁机制进行了相关的抽取,并且封装成了工具类,核心工具类代码如下:

  1. /**
  2. * @author kdaddy@163.com
  3. * @date 2021/1/7 22:36
  4. * 公众号“程序员老猫”
  5. */
  6. @Service
  7. public class RedisLockUtil {
  8. @Autowired
  9. private RedisTemplate redisTemplate;
  10. private String value = UUID.randomUUID().toString();
  11. public Boolean lock(String key){
  12. RedisCallback<Boolean> redisCallback = redisConnection -> {
  13. //表示set nx 存在key的话就不设置,不存在则设置
  14. RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
  15. //设置过期时间
  16. Expiration expiration = Expiration.seconds(30);
  17. byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
  18. byte[] redisValue = redisTemplate.getKeySerializer().serialize(value);
  19. Boolean result = redisConnection.set(redisKey,redisValue,expiration,setOption);
  20. return result;
  21. };
  22. //获取分布式锁
  23. Boolean lock = (Boolean)redisTemplate.execute(redisCallback);
  24. return lock;
  25. }
  26. //释放分布式锁
  27. public Boolean releaseLock(String key){
  28. String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
  29. " return redis.call(\"del\",KEYS[1])\n" +
  30. "else\n" +
  31. " return 0\n" +
  32. "end";
  33. RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class);
  34. List<String> keys = Arrays.asList(key);
  35. boolean result = (Boolean) redisTemplate.execute(redisScript,keys,value);
  36. return result;
  37. }
  38. }

当然相关的业务代码,老猫还是使用了之前并发扣减库存的例子,在此相关的代码以及最终运行的结果也不一一进行举例。小伙伴们可以自行去老猫的github获取相关的示例源码信息,然后运行一下即可。github地址:https://github.com/maoba/kd-distribute。代码已经完成了更新。

Redisson分布式锁

介绍和使用

那么Redisson究竟为何物呢?Redisson 是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。 充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。 (摘自redisson官网:https://redisson.org/)

下面我们来看一下具体用redisson实现分布式锁实战,其实是相当简单的,redisson已经给我们进行了相关的封装,我们开箱即用。

  1. /**
  2. * @author kdaddy@163.com
  3. * @date 2021/1/9 14:23
  4. * @公众号“程序员老猫”
  5. */
  6. public Integer createOrder() throws Exception{
  7. log.info("进入了方法");
  8. Config config = new Config();
  9. config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("ktdaddy");
  10. RedissonClient redissonClient = Redisson.create(config);
  11. RLock rlock = redissonClient.getLock(ORDER_KEY);
  12. rlock.lock(30, TimeUnit.SECONDS);
  13. try {
  14. log.info("拿到了锁");
  15. //....具体可以参考老猫的github
  16. return order.getId();
  17. }catch (Exception e){
  18. e.printStackTrace();
  19. }finally {
  20. rlock.unlock();
  21. }
  22. return null;
  23. }

原理

老猫上文中自己实现redis锁的时候用到了lua脚本,redisson实现的时候其实所有的指令都是通过lua脚本去实现的。上述为redisson的简单架构图,画的比较粗糙。老猫稍微作一下解释。上图中有个看门狗(watchdog)概念。其实这就是一个定时任务,在线程获取锁之后,它会每隔10s帮忙将key的超时时间设置为30s,这样就不会出现线程一直持有锁从而影响其他线程获取锁的问题。小伙伴们可以发现该功能其实就是set px,只是换成了定时任务去实现。当然看门狗的存在保证了出现死锁的情况下会自动释放。

以上只是针对redisson做了一个简单的应用介绍,redisson其实是相当强大的,首先说配置,老猫上述连接redis的方式其实很简单,由于搭建的是单机redis,所以就使用了单机redis的连接方式,当然redisson还支持主从、哨兵、集群等等连接方式;当然锁的种类也相当丰富,以上老猫提供的是可重入锁的流程。其实还包括公平锁、联锁、红锁、读写锁等等,另外的redisson对分布式的容器、队列等等进行了特有的封装,包括分布式的Blocking Queue、分布式Map、分布式Set、分布式List等等。redisson的强大之处老猫在此不一一枚举,有兴趣的小伙伴可以深入研究一下。

缺陷

redis锁可以比较完美地解决高并发的时候分布式系统的线程安全性的问题,但是这种锁机制也并不是完美的。在哨兵模式下,客户端对master节点加了锁,此时会异步复制给slave节点,此时如果master发生宕机,主备切换,slave变成了master。因为之前是异步复制,所以此时正好又有个线程来尝试加锁的时候,就会导致多个客户端对同一个分布式锁完成了加锁操作,这时候业务上会出现脏数据了。关于redis的相关知识,大家可以访问老猫之前的一些文章,包括redis的哨兵模式、持久化等等。

写在最后

本篇主要和小伙伴们分享了redis锁,从老猫自己实现的乞丐版的redis锁到大牛实现的redisson。相信大家也会有一定的收货。其实关于分布式锁,出了redis锁之外还有基于zookeeper的实现。后续老猫会整理并且分享给大家,敬请期待。

当然更多技术干货也欢迎大家搜索关注公众号“程序员老猫”

手撕redis分布式锁,隔壁张小帅都看懂了!的更多相关文章

  1. Redis分布式锁的一点小理解

    1.在分布式系统中,我们使用锁机制只能保证同一个JVM中一次只有一个线程访问,但是在分布式的系统中锁就不起作用了,这时候就要用到分布式锁(有多种,这里指 redis) 2.在 redis当中可以使用命 ...

  2. Redisson实现Redis分布式锁的N种姿势(转)

    Redis几种架构 Redis发展到现在,几种常见的部署架构有: 单机模式: 主从模式: 哨兵模式: 集群模式: 我们首先基于这些架构讲解Redisson普通分布式锁实现,需要注意的是,只有充分了解普 ...

  3. Redis分布式锁的实现

    前段时间,我在的项目组准备做一个类似美团外卖的拼手气红包[第X个领取的人红包最大],基本功能实现后,就要考虑这一操作在短时间内多个用户争抢同一资源的并发问题了,类似于很多应用如淘宝.京东的秒杀活动场景 ...

  4. Redis专题(3):锁的基本概念到Redis分布式锁实现

    拓展阅读:Redis闲谈(1):构建知识图谱 Redis专题(2):Redis数据结构底层探秘 近来,分布式的问题被广泛提及,比如分布式事务.分布式框架.ZooKeeper.SpringCloud等等 ...

  5. Redisson实现Redis分布式锁的底层原理

    一.写在前面 现在面试,一般都会聊聊分布式系统这块的东西.通常面试官都会从服务框架(Spring Cloud.Dubbo)聊起,一路聊到分布式事务.分布式锁.ZooKeeper等知识.所以咱们这篇文章 ...

  6. Zookeeper——基本使用以及应用场景(手写实现分布式锁和rpc框架)

    文章目录 Zookeeper的基本使用 Zookeeper单机部署 Zookeeper集群搭建 JavaAPI的使用 Zookeeper的应用场景 分布式锁的实现 独享锁 可重入锁 实现RPC框架 基 ...

  7. 面试必问:如何实现Redis分布式锁

    摘要:今天我们来聊聊分布式锁这块知识,具体的来看看Redis分布式锁的实现原理. 一.写在前面 现在面试,一般都会聊聊分布式系统这块的东西.通常面试官都会从服务框架(Spring Cloud.Dubb ...

  8. Redis分布式锁 (图解-秒懂-史上最全)

    文章很长,而且持续更新,建议收藏起来,慢慢读! 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : 极致经典 + 社群大片好评 < Java 高并发 三 ...

  9. jedisLock—redis分布式锁实现

    一.使用分布式锁要满足的几个条件: 系统是一个分布式系统(关键是分布式,单机的可以使用ReentrantLock或者synchronized代码块来实现) 共享资源(各个系统访问同一个资源,资源的载体 ...

随机推荐

  1. PyQt(Python+Qt)学习随笔:快速理解Qt 中Action是什么

    一.引言 Qt中Action这个词接触很久了,一直以来没去学习,今天终于准备学习了,查了些资料,初步总结为: Action为界面操作的抽象,应用程序可以通过菜单,工具栏按钮以及键盘快捷键来调用通用的命 ...

  2. geoserver的demo使用过程

    先贴一个效果图,使用的geoserver版本2.18.0,需要对应版本插件netcdf插件[Extensions>Coverage Formats>NetCDF],使用tomcat8进行发 ...

  3. CC-BY-NC-SA (创作共用许可协议)

    创作共用许可协议 (英语:Creative Commons license,简称CC许可) 是一种公共版权许可协议,其允许分发受版权保护的作品. 一个创作共用许可,用于一个作者想给他人分享.使用.甚至 ...

  4. 使用纯js 不导包实现 table 导出 Excel

    1.将js粘贴到项目 2.设置table标签 id3.定义按钮,调用方法即可 1 <!DOCTYPE html> 2 <html lang="zh_CN"> ...

  5. 1.微博回调接口 和绑定user接口

    1.1 oauth/views.py 中添加试图函数 http://192.168.56.100:8888/oauth/weibo/callback/ # 通过vue前端传入的code,微博身份验证c ...

  6. MySQL MHA安装配置

    1.环境规划 192.168.12.131 node01 192.168.12.132 node02 192.168.12.133 node03 2.环境准备 一主两从GTID,略. 3.配置关键程序 ...

  7. [日常摸鱼]poj2420 A Star not a Tree?

    题意:给定$n$个点,找一个点使得这个点到所有点的距离之和最小,求出这个最小距离 传说中的模拟退火- #include<cstdio> #include<ctime> #inc ...

  8. Python简单的验证码生成

    用python生成简单的四位数验证码: 1 import random 2 3 if __name__ == "__main__": #这句话简单的理解就是,只有在本文件下以下的代 ...

  9. HCIP --- BGP属性

    传播范围                 默认值              大优或小优 1. Preference_Value     不传播                      0       ...

  10. C#中string类型必填的诡异问题

    背景 ASP.NETCore3.0项目,使用Swagger接口文档. 之前的项目都是Swashbuckle.AspNetCore-5.0.0 新项目想尝尝鲜,用最新版Swashbuckle.AspNe ...