redis分布式锁常见问题及解决方案

如题所述

第1个回答  2022-07-22
        1.1 锁需要具备唯一性

        1.2 锁需要有超时时间,防止死锁

        1.3 锁的创建和设置锁超时时间需要具备原子性

        1.4 锁的超时的续期问题

        1.5 B的锁被A给释放了的问题

        1.6 锁的可重入问题

        1.7 集群下分布式锁的问题

        问题讲解:

        首先分布式锁要解决的问题就是分布式环境下同一资源被多个进程进行访问和操作的问题,既然是同一资源,那么肯定要考虑数据安全问题.其实和单进程下加锁解锁的原理是一样的,单进程下需要考虑多线程对同一变量进行访问和修改问题,为了保证同一变量不被多个线程同时访问,按照顺序对变量进行修改,就要在访问变量时进行加锁,这个加锁可以是重量级锁,也可以是基于cas的乐观锁.

        解决方案:

        使用redis命令setnx(set if not exist),即只能被一个客户端占坑,如果redis实例存在唯一键(key),如果再想在该键(key)上设置值,就会被拒绝.

        问题讲解:

        redis释放锁需要客户端的操作,如果此时客户端突然挂了,就没有释放锁的操作了,也意味着其他客户端想要重新加锁,却加不了的问题.

        解决方案:

        所以,为了避免客户端挂掉或者说是客户端不能正常释放锁的问题,需要在加锁的同时,给锁加上超时时间.

        即,加锁和给锁加上超时时间的操作如下操作:

  >setnx lockkey true    #加锁操作

  ok

  >expire lockkey 5    #给锁加上超时时间

  ... do something critical ...

  >del lockkey    #释放锁

  (integer) 1

        问题讲解:

        通过2.3加锁和超时时间的设置可以看到,setnx和expire需要两个命令来完成操作,也就是需要两次RTT操作,如果在setnx和expire两次命令之间,客户端突然挂掉,这时又无法释放锁,且又回到了死锁的问题.

        解决方案:

        使用set扩展命令

        如下:

  >set lockkey true ex 5 nx   #加锁,过期时间5s

  ok

  ... do something critical ...

  >del lockkey

        以上的set lockkey true ex 5 nx命令可以一次性完成setnx和expire两个操作,也就是解决了原子性问题.

        问题讲解:

        redis分布式锁过期,而业务逻辑没执行完的场景,不过,这里换一种思路想问题,把redis锁的过期时间再弄长点不就解决了吗?那还是有问题,我们可以在加锁的时候,手动调长redis锁的过期时间,可这个时间多长合适?业务逻辑的执行时间是不可控的,调的过长又会影响操作性能。

        解决方案:

        使用redis客户端redisson,redisson很好的解决了redis在分布式环境下的一些棘手问题,它的宗旨就是让使用者减少对Redis的关注,将更多精力用在处理业务逻辑上。redisson对分布式锁做了很好封装,只需调用API即可。RLock lock = redissonClient.getLock("stockLock");

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

        问题讲解:

        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相同的才可以释放锁

        问题讲解:

        上面我们讲了,为了保证锁具有唯一性,需要使用setnx,后来为了与超时时间一起设置,我们选用了set命令。 在我们想要在加锁期间,拥有锁的客户端想要再次获得锁,也就是锁重入

        解决方案:

       给锁设置hash结构的加锁次数,每次加锁就+1

        问题讲解:

        这一问题是在redis集群方案时会出现的.事实上,现在为了保证redis的高可用和访问性能,都会设置redis的主节点和从节点,主节点负责写操作,从节点负责读操作,也就意味着,我们所有的锁都要写在主redis服务器实例中,如果主redis服务器宕机,资源释放(在没有加持久化时候,如果加了持久化,这一问题会更加复杂),此时redis主节点的数据并没有复制到从服务器,此时,其他客户端就会趁机获取锁,而之前拥有锁的客户端可能还在对资源进行操作,此时又会出现多客户端对同一资源进行访问和操作的问题.

        解决方案:

        使用redlock,原理与zookeeper分布式锁原理相同.多台主机超过半数设置成功则获取锁成功,要注意下主机个数必须是奇数,不过这有效率问题
相似回答