详解redis分布式锁的这些坑


Posted in Redis onMay 19, 2021

一、白话分布式

什么是分布式,用最简单的话来说,就是为了较低单个服务器的压力,将功能分布在不同的机器上面,本来一个程序员可以完成一个项目:需求->设计->编码->测试

详解redis分布式锁的这些坑

但是项目多的时候,一个人也扛不住,这就需要不同的人进行分工合作了

详解redis分布式锁的这些坑

这就是一个简单的分布式协同工作了;

二、分布式锁

首先看一个问题,如果说某个环节被终止或者别侵占,就会发生不可知的事情

详解redis分布式锁的这些坑

这就会出现,设计好的或者设计的半成品会被破坏,导致后面环节出错;

这时候,我们就需要引入分布式锁的概念;

何为分布式锁

当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。

用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

分布式锁的条件

  • 可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。
  • 这把锁要是一把可重入锁(避免死锁)
  • 这把锁最好是一把阻塞锁
  • 这把锁最好是一把公平锁
  • 有高可用的获取锁和释放锁功能
  • 获取锁和释放锁的性能要好

分布式锁的实现

分布式锁的实现由很多种,文件锁、数据库、redis等等,比较多,在实践中,还是redis做分布式锁性能会高一些;

三、redis实现分布式锁

首先看两个命令:

setnx:将 key 的值设为 value,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作。 SETNX 是SET if Not eXists的简写。

127.0.0.1:6379> set lock "unlock"
OK
127.0.0.1:6379> setnx lock "unlock"
(integer) 0
127.0.0.1:6379> setnx lock "lock"
(integer) 0
127.0.0.1:6379>

expire:EXPIRE key seconds

为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除

127.0.0.1:6379> expire lock 10
(integer) 1
127.0.0.1:6379> ttl lock
8
127.0.0.1:6379> get lock
(nil)

基于分布式锁的流程:

详解redis分布式锁的这些坑

这就是一个简单的分布式锁的实现流程,具体代码实现也很简单,就不赘述了;

四、redis实现分布式锁问题

如果出现了这么一个问题:如果setnx是成功的,但是expire设置失败,那么后面如果出现了释放锁失败的问题,那么这个锁永远也不会被得到,业务将被锁死?

解决的办法:使用set的命令,同时设置锁和过期时间

set参数:

set key value [EX seconds] [PX milliseconds] [NX|XX]

EX seconds:设置失效时长,单位秒

PX milliseconds:设置失效时长,单位毫秒

NX:key不存在时设置value,成功返回OK,失败返回(nil)

XX:key存在时设置value,成功返回OK,失败返回(nil)

实践:

127.0.0.1:6379> set unlock "234" EX 100 NX
(nil)
127.0.0.1:6379> 
127.0.0.1:6379> set test "111" EX 100 NX
OK

这样就完美的解决了分布式锁的原子性。

五、用锁遇到过哪些问题?又是如何解决的?未关闭资源

由于当前线程 获取到redis 锁,处理完业务后未及时释放锁,导致其它线程会一直尝试获取锁阻塞,例如:用Jedis客户端会报如下的错误信息

1redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

redis线程池已经没有空闲线程来处理客户端命令。使用原生方法记得关闭!

解决的方法也很简单,只要我们细心一点,拿到锁的线程处理完业务及时释放锁

B的锁被A给释放了

我们知道Redis实现锁的原理在于 SETNX命令。当 key不存在时将 key的值设为 value ,返回值为 1;若给定的 key已经存在,则 SETNX不做任何动作,返回值为 0 。

SETNX key value

我们来设想一下这个场景:A、B两个线程来尝试给key myLock加锁,A线程先拿到锁(假如锁3秒后过期),B线程就在等待尝试获取锁,到这一点毛病没有。

那如果此时业务逻辑比较耗时,执行时间已经超过redis锁过期时间,这时A线程的锁自动释放(删除key),B线程检测到myLock这个key不存在,执行 SETNX命令也拿到了锁。

但是,此时A线程执行完业务逻辑之后,还是会去释放锁(删除key),这就导致B线程的锁被A线程给释放了。

为避免上边的情况,一般我们在每个线程加锁时要带上自己独有的value值来标识,只释放指定value的key,否则就会出现释放锁混乱的场景

一般我们可以设置value为业务前缀_当前线程ID或者uuid,只有当前value相同的才可以释放锁

锁过期了,业务还没执行完

redis分布式锁过期,而业务逻辑没执行完的场景,不过,这里换一种思路想问题,把redis锁的过期时间再弄长点不就解决了吗?

那还是有问题,我们可以在加锁的时候,手动调长redis锁的过期时间,可这个时间多长合适?业务逻辑的执行时间是不可控的,调的过长又会影响操作性能。

要是redis锁的过期时间能够自动续期就好了。

为了解决这个问题我们使用redis客户端redisson,redisson很好的解决了redis在分布式环境下的一些棘手问题,它的宗旨就是让使用者减少对Redis的关注,将更多精力用在处理业务逻辑上。

redisson对分布式锁做了很好封装,只需调用API即可。

RLock lock = redissonClient.getLock("stockLock");

redisson在加锁成功后,会注册一个定时任务监听这个锁,每隔10秒就去查看这个锁,如果还持有锁,就对过期时间进行续期。默认过期时间30秒。这个机制也被叫做:“看门狗”

redis主从复制的坑

redis高可用最常见的方案就是主从复制(master-slave),这种模式也给redis分布式锁挖了一坑。

redis cluster集群环境下,假如现在A客户端想要加锁,它会根据路由规则选择一台master节点写入key mylock,在加锁成功后,master节点会把key异步复制给对应的slave节点。

如果此时redis master节点宕机从节点复制失败,为保证集群可用性,会进行主备切换,slave变为了redis master。B客户端在新的master节点上加锁成功,而A客户端也以为自己还是成功加了锁的。另外如果主从复制延迟同样也会造成加锁和解锁延迟的问题。

此时就会导致同一时间内多个客户端对一个分布式锁完成了加锁,导致各种脏数据的产生。

毕竟redis是保持的AP而非CP,如果要追求强一致性可以使用zookeeper分布式锁。

以上就是详解redis分布式锁的这些坑的详细内容,更多关于redis分布式锁的这些坑的资料请关注三水点靠木其它相关文章!

Redis 相关文章推荐
基于Redis位图实现用户签到功能
May 08 Redis
深入理解redis中multi与pipeline
Jun 02 Redis
redis使用不当导致应用卡死bug的过程解析
Jul 01 Redis
详解redis在微服务领域的贡献
Oct 16 Redis
linux下安装redis图文详细步骤
Dec 04 Redis
基于Redis zSet实现滑动窗口对短信进行防刷限流的问题
Feb 12 Redis
解决Redis启动警告问题
Feb 24 Redis
解决 Redis 秒杀超卖场景的高并发
Apr 12 Redis
Redis官方可视化工具RedisInsight安装使用教程
Apr 19 Redis
Grafana可视化监控系统结合SpringBoot使用
Apr 19 Redis
Redis实现分布式锁的五种方法详解
Jun 14 Redis
基于redis+lua进行限流的方法
Jul 23 Redis
为Java项目添加Redis缓存的方法
redis内存空间效率问题的深入探究
深入浅析Redis 集群伸缩原理
Redis延迟队列和分布式延迟队列的简答实现
基于Redis延迟队列的实现代码
基于Redis实现分布式锁的方法(lua脚本版)
redis三种高可用方式部署的实现
May 11 #Redis
You might like
PHP 命名空间实例说明
2011/01/27 PHP
ThinkPHP模型详解
2015/07/27 PHP
PHP实现图片上传并压缩
2015/12/22 PHP
Thinkphp 框架扩展之行为扩展原理与实现方法分析
2020/04/23 PHP
ExtJs事件机制基本代码模型和流程解析
2010/10/24 Javascript
JavaScript.The.Good.Parts阅读笔记(二)作用域&闭包&减缓全局空间污染
2010/11/16 Javascript
JS解决ie6下png透明的方法实例
2013/08/02 Javascript
Jquery Ajax解析XML数据(同步及异步调用)简单实例
2014/02/12 Javascript
一系列Bootstrap导航条使用方法分享
2016/04/29 Javascript
jQuery代码实现对话框右上角菜单带关闭×
2016/05/03 Javascript
jquery设置表单元素为不可用的简单代码
2016/07/04 Javascript
JS获取IE版本号与HTML设置IE文档模式的方法
2016/10/09 Javascript
PHP+jquery+ajax实现分页
2016/12/09 Javascript
Node.js 实现简单的接口服务器的实例代码
2017/05/23 Javascript
nodejs基于WS模块实现WebSocket聊天功能的方法
2018/01/12 NodeJs
小程序自定义组件实现城市选择功能
2018/07/18 Javascript
对Vue table 动态表格td可编辑的方法详解
2018/08/28 Javascript
js嵌套的数组扁平化:将多维数组变成一维数组以及push()与concat()区别的讲解
2019/01/19 Javascript
vue component 中引入less文件报错 Module build failed
2019/04/17 Javascript
AngularJS实现的鼠标拖动画矩形框示例【可兼容IE8】
2019/05/17 Javascript
Net微信网页开发 使用微信JS-SDK获取当前地理位置过程详解
2019/08/26 Javascript
layui checkbox默认选中,获取选中值,清空所有选中项的例子
2019/09/02 Javascript
解决vue项目F5刷新mounted里的函数不执行问题
2019/11/05 Javascript
深入浅析golang zap 日志库使用(含文件切割、分级别存储和全局使用等)
2020/02/19 Javascript
vue中后端做Excel导出功能返回数据流前端的处理操作
2020/09/08 Javascript
python实现对指定字符串补足固定长度倍数截断输出的方法
2018/11/15 Python
django echarts饼图数据动态加载的实例
2019/08/12 Python
python字典排序的方法
2019/10/12 Python
解决python 在for循环并且pop数组的时候会跳过某些元素的问题
2020/12/11 Python
使用CSS禁止textarea调整大小功能的方法
2015/03/13 HTML / CSS
解决CSS3 transition-delay 属性默认值0不带单位失效的问题
2020/10/29 HTML / CSS
吉力贝官方网站:Jelly Belly
2019/03/11 全球购物
杭州龙健科技笔试题.net部分笔试题
2016/01/24 面试题
《台湾的蝴蝶谷》教学反思
2014/02/20 职场文书
店面销售职位的职责
2014/03/09 职场文书
教育基金募捐倡议书
2014/05/14 职场文书