详解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 相关文章推荐
在K8s上部署Redis集群的方法步骤
Apr 27 Redis
Redis6.0搭建集群Redis-cluster的方法
May 08 Redis
redis实现共同好友的思路详解
May 26 Redis
Redis Cluster集群动态扩容的实现
Jul 15 Redis
Redis 持久化 RDB 与 AOF的执行过程
Nov 07 Redis
解决Redis启动警告问题
Feb 24 Redis
Redis高并发缓存架构性能优化
May 15 Redis
Redis基本数据类型Zset有序集合常用操作
Jun 01 Redis
Redis基本数据类型Set常用操作命令
Jun 01 Redis
关于Redis的主从复制及哨兵问题
Jun 16 Redis
浅谈Redis变慢的原因及排查方法
Jun 21 Redis
Redis+AOP+自定义注解实现限流
Jun 28 Redis
为Java项目添加Redis缓存的方法
redis内存空间效率问题的深入探究
深入浅析Redis 集群伸缩原理
Redis延迟队列和分布式延迟队列的简答实现
基于Redis延迟队列的实现代码
基于Redis实现分布式锁的方法(lua脚本版)
redis三种高可用方式部署的实现
May 11 #Redis
You might like
全国FM电台频率大全 - 30 宁夏回族自治区
2020/03/11 无线电
世界收音机发展史
2021/03/01 无线电
用Socket发送电子邮件(利用需要验证的SMTP服务器)
2006/10/09 PHP
PHP下用rmdir实现删除目录的三种方法小结
2008/04/20 PHP
php 字符转义 注意事项
2009/05/27 PHP
Thinkphp中数据按分类嵌套循环实现方法
2014/10/30 PHP
小议javascript 设计模式 推荐
2009/10/28 Javascript
关于query Javascript CSS Selector engine
2013/04/12 Javascript
原生js模拟淘宝购物车项目实战
2015/11/18 Javascript
JavaScript对象拷贝与赋值操作实例分析
2018/12/10 Javascript
解决vue 单文件组件中样式加载问题
2019/04/24 Javascript
Vue+ElementUI项目使用webpack输出MPA的方法
2019/08/27 Javascript
Bootstrap table 实现树形表格联动选中联动取消功能
2019/09/30 Javascript
Node.js API详解之 zlib模块用法分析
2020/05/19 Javascript
详解Vue的组件中data选项为什么必须是函数
2020/08/17 Javascript
JavaScript实现切换多张图片
2021/01/27 Javascript
Python的Tornado框架实现图片上传及图片大小修改功能
2016/06/30 Python
Sanic框架蓝图用法实例分析
2018/07/17 Python
python3实现爬取淘宝美食代码分享
2018/09/23 Python
django框架之cookie/session的使用示例(小结)
2018/10/15 Python
linux环境中没有网络怎么下载python
2019/07/07 Python
Python操作Sonqube API获取检测结果并打印过程解析
2019/11/27 Python
TensorFlow实现批量归一化操作的示例
2020/04/22 Python
浅谈python出错时traceback的解读
2020/07/15 Python
Python定义一个Actor任务
2020/07/29 Python
Urban Outfitters英国官网:美国平价服饰品牌
2016/11/25 全球购物
计算机专业应届生求职信
2014/04/06 职场文书
宣传工作经验材料
2014/06/02 职场文书
销售队伍口号
2014/06/11 职场文书
教师师德师风自我剖析材料
2014/09/29 职场文书
党的群众路线教育实践活动查摆剖析材料
2014/10/10 职场文书
标准离婚协议书范文下载
2014/11/30 职场文书
2015年数学教师工作总结
2015/05/20 职场文书
财务人员入职担保书
2015/09/22 职场文书
写一个Python脚本自动爬取Bilibili小视频
2021/04/24 Python
html中显示特殊符号(附带特殊字符对应表)
2021/06/21 HTML / CSS