分布式锁:二、Redis锁
时间:2022-07-24
本文章向大家介绍分布式锁:二、Redis锁,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
对于分布式锁的实现,除了redis锁之外,还有很多,像zookeeper,memcache,数据库,chubby等。redis锁因为使用简单,所以被大家广泛使用。
本篇文章主要从以下几个方面来讲解redis锁:
1.redis锁使用的时候,有哪些问题
2.这些问题会导致什么样子的后果
3.应该如何解决这些问题
一、redis锁的实现
加锁命令:
SETNX key value:
当键不存在时,对键进行设置操作并返回成功1,否则返回失败0。
Key是锁的唯一标识,一般按业务来决定命名;
Value 往往用来比较加锁的是哪一个线程或者哪一个消息,
一般使用UUID.randomUUID().toString()方法生成。
例如:setnx(key,1)
解锁命令:
DEL key
通过删除键值对释放锁,以便其他线程可以通过 SETNX 命令来获取锁。
例如:del(key)
锁超时:
EXPIRE key timeout
设置 key 的超时时间,以保证即使锁没有被显式释放,
锁也可以在一定时间后自动释放,避免资源被永远锁住。
例如:expire(key,30),表示30s超时释放锁。
备注:
Redis 2.6.12以上版本为set指令增加了可选参数,伪代码如下:
set(key,1,30,NX),它将加锁和超时两个动作合并到了一起,利用原子性封装了起来。
二、redis锁解决的具体场景
场景1: 为什么redis锁需要设置超时?
原因分析:
1.redis与业务进程之间通常是使用网络通讯的方式进行数据加锁的,而网络通讯就存在丢包的情况。
再加上加锁和解锁是两个操作,这样就会存在锁永远不能释放的问题。
2.除此之外业务进程在加锁之后,也可能panic掉,没有办法去释放掉这个锁,导致分布式锁被永远挂住。
基于上面的两个原因:
分布式锁就需要一个超时时间来主动释放这个锁,防止分布式锁一直被挂住。
redis分布式锁的解决办法,
1.通过加锁和超时两步操作来解决,不过我们最好使用set(key,1,30,NX)这种原子操作。
2.使用setnx和expire两个操作的话,因为它们不是原子性操作,也会存在上面1和2的问题,
进而导致锁被永远锁住,不过也不是没有办法,
我们可以采用lua脚本在redis上面实现的方式来保证它们的原子性。
场景2: 锁超时释放了之后,加锁的业务又过来释放锁怎么办?
具体场景,进程1在超时释放了锁之后,进程2获取到了锁,后来进程1又释放锁,如此以来就有可能导致进程2没有完成就被进程1释放了锁。如下所示:
解决上面问题的关键点,在于我们在释放锁的时候,能够判断出来是不是当前加锁的进程发起的解锁操作。
一般是将进程id作为vlaue放到setnx中,在解锁之前先去判断一把这个锁是不是同一个进程的,
是就允许释放,不是就不允许解锁。
备注:这种操作其实是两步操作,判断锁,释放锁,它们并不是一个原子操作,
如此以来就存在一步操作完成,另一步没有被操作的情况。解决办法是,利用lua脚本实现锁的原子性。
场景3:锁超时释放之后,会不会存在两个业务同时处理加锁的代码的情况?
这个场景与场景2是一个问题的延伸,一旦我们在设置锁的超时时间过短,就会发生这个情况。
锁在被进程2拿走之后,进程1还没有执行代码结束,
如此以来就会存在进程1和进程2同时操作那段公共代码的情况,
这样就会出问题,也显然不是我们期望的场景。
对于这个场景的解决办法,往往有两个:
1.调整超时时间,让业务进程在这段时间之内一定可以执行完毕。
2.启动守护进程,在业务进程没有执行完成的时候,主动的去调节这个超时时间,
让锁的超时时间变长。
场景4:锁被使用之后,其他的业务如何才能获取这个分布式锁?
这个场景,是非常基本的场景,一旦锁被进程1获取之后,在释放锁之前,进程2是怎么知道何时才能够获取到锁呢?
这个解决办法有点像操作系统的轮询和信号量两种。
1.轮询的话,就需要进程2不停的去超时加锁,直到能够加锁成功位置,
不过这种实现比较耗费服务器资源。
2.类似信号量的方法,业务进程2将加锁消息进行订阅操作,
而订阅模块会维护一个消息队列,等到锁释放之后,
便从队列中取出进程2,告知锁已经释放的通知。
进程2收到通知以后,就可以进行加锁操作了。
场景5:redis是集群的话,使用redis分布式锁会不会有问题?
为了保证redis的可用性,往往redis服务器会设置主从,主从服务器中的从服务器在检测到主服务器挂掉之后,就会重新选举一个作为主服务器,而redis锁是操作在主服务器上的。
一旦,发生下面的现象:
1.主服务器刚刚被进程1加锁完成,还没有来得及同步数据到从服务器就挂掉了。
2.从服务器经过选举,选出了新的主服务器。
3.进程2在新的主服务器上加锁成功。
4.如此以来进程1和进程2都会同时操作那段公共代码,这样就会存在问题,算是加锁失败。
针对这种问题,我们其实没有太好的办法,不过还好这种数据的概率比较低。
Redis 分布式锁只能作为一种缓解并发的手段,要完全解决并发问题,仍需要数据库的防并发手段配合使用。
三、参考文档:
https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/
https://mp.weixin.qq.com/s/hoZB0wdwXfG3ECKlzjtPdw
https://mp.weixin.qq.com/s/qJK61ew0kCExvXrqb7-RSg
https://mp.weixin.qq.com/s/8fdBKAyHZrfHmSajXT_dnA
- python 序列化数据:pickle与json ,dumps与loads
- golang继承,和多态
- python 利用random生成验证码与MD5码加密过程
- Java内部类的继承
- Java继承类中static成员函数的重写
- Search for a range寻找上下界-Leetcode
- Basic Calculator 基本计算器-Leetcode
- python yield函数深入浅出理解
- 十分钟搞定 Tensorflow 服务
- datapump跨平台升级迁移的总结 (r8笔记第77天)
- Java中isAssignableFrom()方法与instanceof()方法用法
- 与Ajax同样重要的jQuery(1)
- Java中Class类详解、用法及泛化
- python 函数编程的位置参数、默认参数、关键字参数以及函数的递归
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 详解用Python爬虫获取百度企业信用中企业基本信息
- PHP连接及操作PostgreSQL数据库的方法详解
- 使用Keras建立模型并训练等一系列操作方式
- PHP获取ttf格式文件字体名的方法示例
- PHP iconv()函数字符编码转换的问题讲解
- 使用PHP反射机制来构造"CREATE TABLE"的sql语句
- PHP PDOStatement::fetch讲解
- 解决Pytorch自定义层出现多Variable共享内存错误问题
- PHP观察者模式定义与用法实例分析
- ThinkPHP5.1表单令牌Token失效问题的解决
- PHP设计模式之工厂模式(Factory Pattern)的讲解
- keras K.function获取某层的输出操作
- 浅谈sklearn中predict与predict_proba区别
- 解决Keras中循环使用K.ctc_decode内存不释放的问题
- PDO::getAvailableDrivers讲解