Redis分布式锁面临的问题和解决方案

笔名:  haibiscuit

博客园: https://www.cnblogs.com/haibiscuit/

Git地址: https://github.com/haibiscuit?tab=repositories  (欢迎star)

本项目地址: https://github.com/haibiscuit/StudyBook

尊重笔者的劳动成果,未经允许请不要转载

前言:

    刚面试了一场,理所当然的挂了,故写此文给自己压压惊,顺畅一下我这委屈的心灵.

正文:

    一: 分布式锁面临的问题

        1.1 锁需要具备唯一性

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

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

        1.4 锁的超时问题

        1.5 锁的可重入问题

        1.6 集群下分布式锁的问题

        1.7 redis分布式锁需要考虑的其他问题

    二: 分布式锁面临问题的讲解和解决方案

        2.1 锁需要具备唯一性

        问题讲解:

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

        解决方案:

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

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

        问题讲解:

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

        解决方案:

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

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

  >setnx lockkey true    #加锁操作

  ok

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

  ... do something critical ...

  >del lockkey    #释放锁

  (integer) 1

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

        问题讲解:

        通过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两个操作,也就是解决了原子性问题.

                2.4 锁的超时问题

        问题讲解:

        虽然上面给锁加上了超时时间,但是客户端并不能一定在超时时间之内完成定时任务,所以,即使当前客户端没有完成任务,此时又会有其他的客户端设置锁成功,此时同一资源将会面临多个客户端同时操作的问题.

        解决方案:

        客户端可以在锁设置成功之后,进行定时任务,在锁超时之前使用lua脚本删除锁并重新设置锁和超时时间.

        当然,这里为什么会使用lua来完成操作呢,其实和上面的原子性问题一样,在删除锁和重新设置锁和锁的超时时间之间,可能面临其他的客户端将锁资源占有,而lua具有原子性的特性,删除锁和重新加锁这两个操作要么都完成,要么都不完成.

        2.5 锁的可重入问题

        问题讲解:

        上面我们讲了,为了保证锁具有唯一性,需要使用setnx,后来为了与超时时间一起设置,我们选用了set命令.

        在我们想要在加锁期间,拥有锁的客户端想要再次获得锁,也就是锁重入,当然,这里的问题和2.5问题类似.

        解决方案:

        同样,我们可以选择使用lua脚本的方案,将锁重新删除和设置.

 

        2.6 集群下分布式锁的问题

        问题讲解:

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

        解决方案:

        这一问题准备单独成文进行讨论,因为并不是一两句话可以说清楚.

 

        2.7 redis分布式锁需要考虑的其他问题

        以上讨论了redis在业务逻辑上一定会遇到的问题,解决方案可能也就局限于我上面所讨论的方案.

        当然,还有逻辑需要根据程序员自己的场景进行选定.

        例如:

              (1) 设置键时,键资源从哪获取

              其中涉及多客户端的资源共识问题,简单的分析就是,你怎么确定在多个客户端对同一资源进行加锁操作的时候,这个键(key)就需要多个客户端一致,否则,我怎么保证键在多个客户端的唯一性呢.

              (2) 释放锁时只能由拥有锁资源的客户端释放

             当然,这个问题是由于你加锁和解锁逻辑写的不对的情况.根据上面分析,只要解决上面所有对redis分布式锁的所有问题,这个客户端锁释放的问题一般碰不到.

             不过,这也是细节问题,需要将该问题考虑在业务逻辑中

    三: 总结

        redis分布式锁在集群下出现的问题,我将会在另外一篇文章中讲到

        希望自己的工作赶快稳定下来,加油吧,屌丝

posted @ 2020-04-14 17:06  haibiscuit  阅读(19206)  评论(2编辑  收藏  举报