Redis分布式锁
# Redis分布式锁
基于Redis的分布式锁一般有两个SETNX和Redission
# SETNX
获取锁:通过setNx命令 释放锁:DEL key 命令-----手动释放,或者超时释放,给锁添加一个超时时间;(超时时间的设置需要考量,不能太长)
在极端情况:业务阻塞导致锁提前释放了;其他线程一上来,业务没执行完,线程1这时候执行完了,把线程2的锁给删掉;此时就会导致其他线程获取到锁,可能发生并发问题。为解决该问题需要在删除的时候检查一下该锁是否是本线程的,可以考虑存入线程标识来作区分。
但是在极端情况:就算加了线程判断标志,当要释放锁的时候,线程阻塞;轮到释放了,但没有释放,触发了锁的超时释放,也会导致其他线程乘虚而入;问题的原因在于锁标志和释放锁标志是两个动作;需要让他们一起执行,需要使用lua脚本来保证原子性。
# 存在的问题
基于setnx实现的分布式锁存在以下问题
- 不可重入
- 不可重试
- 不可设置过期时间
- 不能解决Redis主从时,由于主节点宕机导致的多个线程获取锁的问题
市面上已经有了比较成熟的 解决方案 Redission
# Redission
为实现锁的可重入。Redission利用Hash结构,记录线程标识,和获取锁的次数,引入了一个计数器;方法A里面调方法B,A、B都要同一把锁,A一拿到锁,计数器+1 ,B拿到锁,计数器也+1,B执行完逻辑,计数器-1;A当业务执行完成之后,计数器-1,最后判断计数器的数是否为0,为0 ,说明所有业务执行完成,最后释放锁;由于代码逻辑复杂,为了保证原子性,所以最后用lua脚本编写来保证原子性,
为实现锁的可重试:利用信号量和消息订阅Pubsub机制,如果第一次获取锁失败,不是立即失败,而是等待释放锁的消息,获取锁成功的线程释放锁的时候会发送消息,从而被捕获到;当线程得到消息时,就可以重新获取锁,如此反复;超过了等待时间,就不会重试了;由于使用了等待、唤醒这样的方案,cpu的性能也不会过多的消耗;
锁超时释放:基于看门狗机制,获取锁成功后就会用另一个线程来监控业务,默认过期时间是30s,每1/3过期时间刷新一次;
为解决主从的问题;使用RedLock,其要求加锁的时候,Redis集群里的半数以上的节点都要加锁成功,不然就失败,算法的思想是让客户端向 Redis 集群中的多个独立的 Redis 实例依次请求申请加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。即使部分 Redis 节点出现问题,只要保证 Redis 集群中有半数以上的 Redis 节点可用,分布式锁服务就是正常的。Redlock 是直接操作 Redis 节点的,并不是通过 Redis 集群操作的,这样才可以避免 Redis 集群主从切换导致的锁丢失问题。但该方案,性能不高,较为复杂,且Redis主从问题的发生属于小概率事件,如果是在是有要求,建议采用基于CP思想的Zookeeper来实现分布式作锁。