redis分布式锁逻辑分析与问题解决方案

1 篇文章 0 订阅

锁是一个很重要也很基础的概念,锁可以看成是多线程情况下访问共享资源的一种线程同步机制。这是对于单进程应用而言的,即所有线程都在同一个JVM进程里的时候,使用Java语言提供的锁机制可以起到对共享资源进行同步的作用。如果分布式环境下多个不同线程需要对共享资源进行同步,比如A线程和B线程共同争抢同一资源,那么用Java的锁机制就无法实现了,这个时候就必须借助分布式锁来解决分布式环境下共享资源的同步问题。

Redis分布式锁方案一

使用Redis实现分布式锁最简单的方案是在获取锁之前先查询一下以该锁为key对应的value存不存在

如果存在,则说明该锁被其他客户端获取了,否则的话就尝试获取锁

获取锁的方法很简单,只要以该锁为key,设置一个随机的值就行了

因此,获取锁的过程可以用如下伪代码实现:

以Java语言为例,我们一般获取锁后会将释放锁的代码放在finally块中

这样做的好处是即使在使用锁的过程中出现异常,也能顺利将锁释放掉。

用伪代码描述如下:

其中,getLock方法的伪代码上文已经给出,releaseLock方法是释放锁的方法,在该方案中,只是简单地删除掉key,就不给出伪代码了。

代码块中,即使try代码块中抛出异常,最终也会执行finally代码块

考虑这样一种情况:代码执行到doSomething()方法的时候,服务器宕机了,这个时候finally代码块就没法被执行了

因此在这种情况下,该锁不会被正常释放,在上述案例中,可能会导致任务漏算。

因此,这种方案的第一个问题是会出现锁无法正常释放的风险,解决这个问题的方法也很简单,Redis设置key的时候可以指定一个过期时间,只要获取锁的时候设置一个合理的过期时间,那么即使服务器宕机了,也能保证锁被正确释放。

该方案的另外一个问题是,获取到的锁不一定是排他锁,也就是说同一把锁同一时间可能被不同客户端获取到。

仔细分析一下getLock方法,该方法并不是原子性的,当一个客户端检查到某个锁不存在,并在执行setKey方法之前,别的客户端可能也会检查到该锁不存在,并也会执行setKey方法,这样一来,同一把锁就有可能被不同的客户端获取到了。

Redis分布式锁方案二

上一小节的方案有2个缺点,一个是获取的锁可能无法释放,另一个是同一把锁在同一时间可能被不同线程获取到。

通过查看Redis文档,可以找到Redis提供了一个只有在某个key不存在的情况下才会设置key的值的原子命令,该命令也能设置key值过期时间

因此使用该命令,不存在上述方案出现的问题,该命令为:

SET my_key my_value NX PX milliseconds

其中,NX表示只有当键key不存在的时候才会设置key的值,PX表示设置键key的过期时间,单位是毫秒。

如此一来,获取锁的过程可以用如下伪代码描述:

其中,setKeyOnlyIfNotExists方法表示的是原子命令SET my_key my_value NX PX milliseconds。

如此一来,获取锁的代码应该就没什么问题了,但是这种方案还是会有其他问题。

因为现在我们设置key的时候也设置了过期时间,所以原来的释放锁的代码现在看来就有问题了。

考虑这样一种情况:客户端A获取锁的时候设置了key的过期时间为2秒,然后客户端A在获取到锁之后,业务逻辑方法doSomething执行了3秒(大于2秒),当执行完业务逻辑方法的时候,客户端A获取的锁已经被Redis过期机制自动释放了,因此客户端A在获取锁经过2秒之后,该锁可能已经被其他客户端获取到了。

当客户端A执行完doSomething方法之后接下来就是执行releaseLock方法释放锁了,由于前面说了,该锁可能已经被其他客户端获取到了

因此这个时候释放锁就有可能释放的是其他客户端获取到的锁。

Redis分布式锁方案三

有一个很简单的方法是,我们设置key的时候,将value设置为一个随机值r

当释放锁,也就是删除key的时候,不是直接删除,而是先判断该key对应的value是否等于先前设置的随机值

只有当两者相等的时候才删除该key,由于每个客户端产生的随机值是不一样的,这样一来就不会误释放别的客户端申请的锁了。

新的释放锁的方案用伪代码描述如下:

其中,getKey方法就是Redis的查询key值的方法

deleteKey就是Redis的删除key值的方法,在此不给出伪代码了。

这种方案也是有问题的。

原因在于上述释放锁的操作不是原子性的,不是原子性操作意味着当一个客户端执行完getKey方法并在执行deleteKey方法之前,也就是在这2个方法执行之间,其他客户端是可以执行其他命令的。

考虑这样一种情况,在客户端A执行完getKey方法,并且该key对应的值也等于先前的随机值的时候,接下来客户端A将会执行deleteKey方法。

假设由于网络或其他原因,客户端A执行getKey方法之后过了1秒钟才执行deleteKey方法,那么在这1秒钟里,该key有可能也会因为过期而被Redis清除了

这样一来另一个客户端,姑且称之为客户端B,就有可能在这期间获取到锁,然后接下来客户端A就执行到deleteKey方法了,如此一来就又出现误释放别的客户端申请的锁的问题了。

Redis分布式锁方案四

既然方案三的问题是因为释放锁的方法不是原子操作导致的,那么我们只要保证释放锁的代码是原子性的就能解决该问题了。

在Redis中执行原子操作不止有通过官方提供的命令的方式,还有另外一种方式,就是Lua脚本。因此,方案三中的释放锁的代码可以用以下Lua脚本来实现:

其中ARGV[1]表示设置key时指定的随机值。由于Lua脚本的原子性,在Redis执行该脚本的过程中,其他客户端的命令都需要等待该Lua脚本执行完才能执行,所以不会出现方案三所说的问题。至此,使用Redis实现分布式锁的方案就相对完善了。

上述分布式锁的实现方案中,都是针对单节点Redis而言的,然而在生产环境中,我们使用的通常是Redis集群,并且每个主节点还会有从节点。由于Redis的主从复制是异步的,因此上述方案在Redis集群的环境下也是有问题的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: Redis分布式锁Redission都是解决高并发中分布式锁问题的方法。Redis分布式锁是通过使用K-V存储来判断是否拥有,避免了释放他人的问题。当业务没有执行完毕但是已经过期时,可以采用守护线程的方式来定期检查是否过期,并延长的过期时间,也就是的续期机制。而Redission是一种实现分布式锁解决方案,它首先获取,然后尝试加,加成功后执行业务逻辑,最后释放Redission解决了Redis实现分布式锁中的过期和释放他人问题,通过内部机制和看门狗机制来保证的有效性。然而,Redission在Redis主从架构下存在高一致性问题,解决高一致性问题可以使用红或者zk,但这可能会牺牲高可用性。总的来说,Redis分布式锁Redission都是解决高并发中分布式锁的方法,但需要根据具体情况选择合适的方案。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [RedisRedission两种方式实现分布](https://blog.csdn.net/weixin_45150104/article/details/125131846)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值