分布式锁
- setnx key value 当key不存在的情况下,将键key的值设置为value,若key已经存在,则setnx不做任何操作。
有返回结果,返回是true表示setnx成功。表示抢到了锁,否则就是失败。不管多少个请求并发,因为redis是单线程,所以只会有一个请求返回true,即只会有一个请求取到锁。
使用完之后记得释放锁,
不管多少个请求,redis都会排队,因为redis是单线程的。 - 上面的分布式锁是最简单的,有bug
(1)如果中间拿到锁的请求成功了,但是中间的逻辑错误了,那么释放锁的步骤就不会执行,就会没有请求能够拿到锁,所以释放锁的代码应该放在finally中。
(2)如果直接down机,即中间卡顿,长时间执行阻塞,也会导致,锁无法释放,redis的key一直存在,没有办法释放key,即使在finally中也没有用;此时的解决方法就是给redis,setnx key的时候加上超时时间,即
表示超时10s,10s之后就会自动清理
(3)上面即使设置了过期时间,但是是两个步骤,即当获得key成功,但是设置过期时间的时候失败了,还是会引发问题。此时就可以调用新的API。
该命令放到redis中的时候,会直接将set和设置超时时间的时候保证其原子性。要么同时成功,要么同时失败。
(4)此时的redis实现分布式锁,如果并发量不大已经差不多了。但是并发量大的时候还是不行。比如:当我们执行完整个过程需要15s,但是我们设置其redis的超时时间是8s,所以再来一个线程也会set key即加锁,此时第一个线程执行完了,他会调用释放锁的步骤,此时线程1就会把线程2加的锁释放掉。这里如果并发量大的时候,我们的分布式锁有可能就会永久失效,set key之后,其他线程有可能又给删掉了。
此时如何解决呢?线程A加的锁,只有线程A能够释放,其他的线程不能释放其他线程的锁。此时可以加一个UUID,判断当前线程的uuid是否是自己的。在set的时候key是锁名称,value是各自的uuid
(5)此时还是有点不完善,因为设置的redis的过期时间还是不完善,因为还是有可能多个个线程同时获得该锁,因为超过过期时长,将锁释放了,即使不是自己手动delete锁。此时又应该如何解决呢?这就需要锁续命了。拿到锁的时候再起一个线程(自我猜测起一个守护线程),该线程用来监听,每隔一段时间,(可以是锁有效时间的1/3),将该锁重新设置其超时时间。此时只要主线程不断,锁就不会超时。此时遇到各种问题都可以适用,定时器down机,主线程死亡都可以正常继续执行。
(6)福利来了,我们其实不用自己实现,我们现在可以直接时候用框架redission,这是redis的一个客户端,适用于分布式场景下的redis的使用,要想使用redission其实是非常简单的。
6.1 导入pom
6.2 配置
6.3 注入redission。
6.4 使用
一个redisssionLock.lock就可以实现我们上面讲的所有的逻辑(分线程,过期时长,setnx都实现了),然后unlock就可以释放锁。
(7)此时如果我们使用的是redis的主从架构,主从复制的时候,我们在主节点设置了key是成功的,但是复制到从节点的时候(从节点还没有复制),此时主节点挂了,这时主从切换,从变为主了,此时,我们再来一个线程,发现新的线程也可以加锁(因为第一个线程加的锁是在master中的,从节点还没复制过来,所以切换的时候,从变为主,此时从没有记录该key被获取),所以还是有问题。
(8)redis的分布式锁天生就是有性能问题的,redis是单线程的,即使并发请求也会对其进行排队。那么如何提高redis分布式锁的性能问题呢?