分布式锁 哨兵模式_手撕redis分布式锁,隔壁张小帅都看懂了!

前言

上一篇老猫和小伙伴们分享了为什么要使用分布式锁以及分布式锁的实现思路原理,目前我们主要采用第三方的组件作为分布式锁的工具。上一篇运用了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中存入一个带有过期时间的值。具体上述设值语句解释如下:

resource_name:资源名称,可以根据不同的业务区分不同的锁。(其实就是对应我们上一篇myql锁中的business_code)。

my_random_value:随机值,每个线程的随机值都不相同,主要用于释放锁的时候用来校验。

NX:key不存在的时候设置成功,key存在则设置不成功。

PX:自动失效时间,如果出现异常情况,锁可以过期实现,因此达到了自动释放。

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

if redis.call("get",KEYS[1]) == ARGV[1] then

return redis.call("del",KEYS[1])

else

return 0

end

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

如下图:

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

代码实现方式

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

/**

* @author kdaddy@163.com

* @date 2021/1/7 22:36

* 公众号“程序员老猫”

*/

@Service

public class RedisLockUtil {

@Autowired

private RedisTemplate redisTemplate;

private String value = UUID.randomUUID().toString();

public Boolean lock(String key){

RedisCallback redisCallback = redisConnection -> {

//表示set nx 存在key的话就不设置,不存在则设置

RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();

//设置过期时间

Expiration expiration = Expiration.seconds(30);

byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);

byte[] redisValue = redisTemplate.getKeySerializer().serialize(value);

Boolean result = redisConnection.set(redisKey,redisValue,expiration,setOption);

return result;

};

//获取分布式锁

Boolean lock = (Boolean)redisTemplate.execute(redisCallback);

return lock;

}

//释放分布式锁

public Boolean releaseLock(String key){

String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +

" return redis.call(\"del\",KEYS[1])\n" +

"else\n" +

" return 0\n" +

"end";

RedisScript redisScript = RedisScript.of(script,Boolean.class);

List keys = Arrays.asList(key);

boolean result = (Boolean) redisTemplate.execute(redisScript,keys,value);

return result;

}

}

当然相关的业务代码,老猫还是使用了之前并发扣减库存的例子,在此相关的代码以及最终运行的结果也不一一进行举例。小伙伴们可以自行去老猫的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已经给我们进行了相关的封装,我们开箱即用。

/**

* @author kdaddy@163.com

* @date 2021/1/9 14:23

* @公众号“程序员老猫”

*/

public Integer createOrder() throws Exception{

log.info("进入了方法");

Config config = new Config();

config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("ktdaddy");

RedissonClient redissonClient = Redisson.create(config);

RLock rlock = redissonClient.getLock(ORDER_KEY);

rlock.lock(30, TimeUnit.SECONDS);

try {

log.info("拿到了锁");

//....具体可以参考老猫的github

return order.getId();

}catch (Exception e){

e.printStackTrace();

}finally {

rlock.unlock();

}

return null;

}

原理

老猫上文中自己实现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的实现。后续老猫会整理并且分享给大家,敬请期待。

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

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

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

相关文章

.某学校的学生公寓有14栋楼,用A~N这14个大写字母的其中一个代表楼号,每栋楼的层数为6层,用1~6六个数字表示。每层楼有40个房间,编号为01~40。具体表示一个宿舍房间时,用1个字母加3位数字表

软件测试——等价类划分法 1.某学校的学生公寓有14栋楼,用A~N这14个大写字母的其中一个代表楼号,每栋楼的层数为6层,用1~6六个数字表示。每层楼有40个房间,编号为01~40。具体表示一个宿舍房间时…

python 贪吃蛇大作战_python实现简单贪吃蛇游戏

本文实例为大家分享了python实现贪吃蛇游戏的具体代码,供大家参考,具体内容如下代码:from turtle import *from random import randrangefrom time import sleep### 定义变量snake [[0,0],[10,0],[20,0],[30,0],[40,0],[50,0]]apple_x rand…

如何免费下载和安装Windows 11

微软今天放出了 Windows 11 Build 22000.51 预览版,如果你想要尝鲜或者冒险,那么本文分享如何免费下载和安装 Windows 11 的小技巧。再次需要提醒的是,目前 Windows 11 系统极不稳定,充斥着大量 BUG,可能会导致系统崩溃…

python函数被调用才能执行吗_python3x函数在不被调用的情况下运行

这是我的密码code__author__ Jared Reabow__name__ Assignment 2 Dice game#Date created: 14/11/2014#Date modified 17/11/2014#Purpose: A game to get the highest score by rolling 5 virtual dice.import random#pre defined variablesNumberOfDice 5 #this variable …

Java 为什么数组下标只能为int不能为long?int32位,为何最大值不是2^32 -1 ? java基本类型取值范围

一个小标为int的byte数组全部放满东西需要的内存为(2^31-11)*1b2Gb0.25GB 一个小标为long的byte数组全部放满东西需要的内存为(2^631)*1b 需要的内存太大 !! byte: byte 数据类型是8位、有符号的&#xf…

输出分组_通过分组卷积的思想,巧妙的代码实现动态卷积(Dynamic Convolution)

论文的题目为《Dynamic Convolution: Attention over Convolution Kernels》paper的地址https://arxiv.org/pdf/1912.03458.pdf代码实现地址,其中包含一维,二维,三维的动态卷积;分别可以用于实现eeg的处理,正常图像的处…

java基本数据类型转为String类型的4种常见方法

1.toString 先把基本数据类型装箱,再用对象的toString()方法 2.String类的valueOf方法 这是一个静态方法,几乎可以把各种类型转换为字符串 String.valueOf(); 注意区别包装类的valueOf方法,那个也是静态方法,返回的是包装类型。 3.加一…

北大青鸟消防控制器组网_北大青鸟JBF-61S20防火门监控器控制器接线示意图

北大青鸟JBF-61S20防火门监控器控制器接线示意图一,北大青鸟JBF-61S20防火门监控器控制器接线示意图功能防火门控制及状态 监测功能手动、自动关闭常开防火门;实时监控常开或常闭防火门的状态信息,并进行上报和显示。联网功能监控器可以与青鸟…

Java的Comparator排序(升序降序)理解

Java的Comparator排序(升序降序)理解 int compare(T o1, T o2);   这里o1表示位于前面的对象,o2表示后面的对象 返回-1(或负数),表示不需要交换01和02的位置,o1排在o2前面,asc 返…

sqlserver发布订阅无法初始化快照_SQLServer2008R2 发布订阅及相关问题解决办法

前言:前两天接到领导的任务,将一个系统A的客户数据同步到另一个系统B中,以后客户录入入口只有A系统,B系统不提供录入入口,因为各种原因不能使用接口方式A系统和B系统直接交互同步,只能通过数据库重A库同步到…

JAVA中基本类型Boolean占几个字节

今天在整理JAVA基础知识时发现几大基本数据类型的封装类都有其BYTES值,也就是位数,除了Boolean。特意查了下资料,发现有几大说法,如下: 1.单个的boolean 类型变量在编译的时候是使用的int 类型。 boolean atrue;//这个a在JVM中占4个字节即:32位。 2.bo…

怎么在linux上修改mysql端口映射_如何在Linux中更改默认的MySQL / MariaDB端口

在本指南中,我们将学习如何更改MySQL / MariaDB数据库在CentOS 7和基于Debian的Linux发行版中绑定的默认端口。 MySQL数据库服务器在Linux和Unix下运行的默认端口是3306 / TCP 。为了在Linux中更改默认的MySQL / MariaDB数据库端口,请通过执行以下命令打…

java 泛型的上限与下限、泛型通配符、泛型上下限

java 泛型的上限与下限 设置泛型对象的上限使用extends,表示参数类型只能是该类型或该类型的子类&#xff1a; 声明对象&#xff1a;类名<? extends 类> 对象名 定义类&#xff1a;类名<泛型标签 extends 类>{} 设置泛型对象的下限使用super,表示参数类型只能是…

hashmap为什么线程不安全_StringBuilder为什么线程不安全?

点击上方“Java知音”&#xff0c;选择“置顶公众号”技术文章第一时间送达&#xff01;作者&#xff1a;千山juejin.im/post/5d6228046fb9a06add4e37fe引言面试官&#xff1a;StringBuilder和StringBuffer的区别在哪&#xff1f;我&#xff1a;StringBuilder不是线程安全的&am…

java中的重量级与轻量级概念

首先轻量级与重量级是一个相对的概念&#xff0c;主要是对应用框架使用方便性和所提供服务特性等方面做比较的。 比方说EJB就是一个重量级的框架&#xff0c;因为它对所编写的代码有限制&#xff0c;同时它也提供分布式等复杂的功能。 相比之下&#xff0c;Spring就是轻量级框架…

java中持久化是什么意思?

什么是持久化&#xff1f; 要想说明这个名词&#xff0c;还要从上世纪70年代说起&#xff0c;数据库技术兴起&#xff0c;这时的软件结构发展为双层结构。在双层结构中实现了的数据存放与应用程序分离&#xff0c;构成了现代软件模型的雏形。但是&#xff0c;随着软件体量越来越…

mysql主从报错_Mysql主从报错锦集

前言在发生故障切换后&#xff0c;经常遇到的问题就是同步报错&#xff0c;下面是最近收集的报错信息。记录删除失败在master上删除一条记录&#xff0c;而slave上找不到Last_SQL_Error: Could not execute Delete_rows event on table hcy.t1;Cant find record in t1,Error_co…

JAVA中的那些名词解释

1.JDO: (Java Data Object )是Java对象持久化的新的规范&#xff0c;也是一个用于存取某种数据仓库中的对象的标准化API.作用:用于存取某种数据仓库中的对象 2.JPA: JPA是Java Persistence API的简称&#xff0c;中文名Java持久层API&#xff0c;是JDK 5.0注解或XML描述对象&a…

mysql资质_MySQL语句与Java代码实现按需过滤企业员工的资质证书

在企业ERP中&#xff0c;每位员工拥有多个资质证书&#xff0c;资质证书有种类、名称和登记时间&#xff0c;以及变动时间和结束时间的主要属性&#xff1b;现在有一个需求&#xff0c;员工在系统查看自己的信息之时&#xff1a;同一种类、同一名称、同一登记时间的资质证书&am…

java对象的序列化和反序列化详细解释

java对象的序列化和反序列化是什么意思 1、序列化是干啥用的&#xff1f; 序列化的原本意图是希望对一个Java对象作一下“变换”&#xff0c;变成字节序列&#xff0c;这样一来方便持久化存储到磁盘&#xff0c;避免程序运行结束后对象就从内存里消失&#xff0c;另外变换成字…