写点什么

redis 分布式锁的这些坑,我怀疑你是假的开发

发布于: 2021 年 02 月 05 日

摘要:用锁遇到过哪些问题?


一、白话分布式


什么是分布式,用最简单的话来说,就是为了较低单个服务器的压力,将功能分布在不同的机器上面;


就比如:


本来一个程序员可以完成一个项目:需求->设计->编码->测试



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



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


二、分布式锁


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



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


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


何为分布式锁?


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

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


分布式锁的条件:


  • 可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。

  • 这把锁要是一把可重入锁(避免死锁)

  • 这把锁最好是一把阻塞锁

  • 这把锁最好是一把公平锁

  • 有高可用的获取锁和释放锁功能

  • 获取锁和释放锁的性能要好


分布式锁的实现:


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


三、redis 实现分布式锁


首先看两个命令:


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



expire: EXPIRE key seconds


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



基于分布式锁的流程:



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


四、redis 实现分布式锁问题


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


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


set参数:



实践:



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


用锁遇到过哪些问题?又是如何解决的?


未关闭资源


由于当前线程 获取到 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 即可。


1 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 分布式锁?易踩得坑》,原文作者:minjie 。


点击关注,第一时间了解华为云新鲜技术~


发布于: 2021 年 02 月 05 日阅读数: 37
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
redis分布式锁的这些坑,我怀疑你是假的开发