分布式架构中,高并发场景下,简单的线程锁无法保证线程安全,我们需要使用分布式锁来保证线程安全。在诸多的分布式锁实现中,redis分布式锁的应用应该是最为常见的,下面就让我们来看一下分布式锁实现中需要考虑到的诸多方面:
首先使用redis实现分布式锁需要用到redis的setnx key命令,(如果key不存在才允许设置)
下面我们来看一下redis分布式锁需要考虑的问题:
1、设置锁之后,解锁失败,即使使用try finally也是无法解决无法解锁的问题,因为分布式锁的设置时多个后端系统在争抢,如果有一个系统出现一些原因需要被kill掉,但是此时正是这个系统持有锁,这时try finally也不会被走到,一次会造成死锁。
2、针对1中的死锁问题,我们可以给锁设置一个过期时间,再设置过期时间时使用setnx显然是不合适的,因为setnx和设置expire过期时间显然不是原子性操作,因此就会出现设置了锁但是设置过期时间失败,因此我们需要使用较新版本的redis提供的set命令
set key value [EX seconds] [PX milliseconds] [NX|XX],这时就能保证设置锁和过期时间的原子性操作
3、解决了1,2问题,看似redis分布式锁已经很完善了,先不要着急,我们设想一下一个加锁的业务需要10秒完成业务,但是我们设置的锁的过期时间只有5秒(这里时间只是一个假设,因为无论设置多长时间的锁都有可能因为各种原因造成枷锁业务耗时比锁过期时间长的问题),此时当业务执行到5秒时还未结束,但是锁已经被释放,另外得到锁的线程需要8秒执行完毕,很显然此时第一个持有锁的线程在5秒之后结束主动释放锁,现在第三个线程又可以持有锁去执行,如此循环往复,造成锁的持久失效,想要解决这个问题,我们可以针对key的值进行考虑,让每次加锁的值具有唯一性(如使用uuid),这样在释放锁的时候,就可以判断是否uuid相同,如果相同就主动释放锁,如果不同就表示当前线程所持有的锁已经过期,因此当前线程不需要在进一步加锁。
4、在解决了上述1,2,3问题后,看似redis分布式锁已经相当完美,但是仔细想想加锁时间还是一个无法逃避的问题,如果锁过期时间过长,一旦有一个线程在执行过程中被kill掉未能释放锁,那么其他线程就会等待很长时间,这样可能会造成一些问题,如果加锁时间过短,虽然经过第3步之后不会永久锁失效,但是很显然两个线程会造成线程不安全,此时可以考虑使用重开一个daemon线程不断地watch这个锁(随主线程结束而结束),每次watch到这个锁,就会增加这个锁的生存时间。
5、除了1,2,3,4,之外,我们还需要考虑到redis的部署方式,如果是redis单机部署,很显然上面的redis分布式锁近乎完美,但是现在的互联网公司追求的是高可用,稍微不错的公司都不会使用redis单机部署,很显然redis高可用集群架构(主从、哨兵、cluster),如果当一把锁上在master上,但是这把锁还未来的及同步到slave上时,master挂了,现在问题就来了,此时就会同样造成其他线程同样可以持有锁,造成线程不安全(即便这种不安全只会在master挂掉时出现一次),但是这也是我们需要考虑的问题。
原文地址:https://www.cnblogs.com/limaomao/p/11220704.html