Loading

Redis分布式锁

Redis分布式锁

基于Redis的分布式锁一般有两个SETNX和Redission

SETNX

获取锁:通过setNx命令
释放锁:DEL key 命令-----手动释放,或者超时释放,给锁添加一个超时时间;(超时时间的设置需要考量,不能太长)

在极端情况:业务阻塞导致锁提前释放了;其他线程一上来,业务没执行完,线程1这时候执行完了,把线程2的锁给删掉;此时就会导致其他线程获取到锁,可能发生并发问题。为解决该问题需要在删除的时候检查一下该锁是否是本线程的,可以考虑存入线程标识来作区分。

Snipaste_20230801_171736.png

但是在极端情况:就算加了线程判断标志,当要释放锁的时候,线程阻塞;轮到释放了,但没有释放,触发了锁的超时释放,也会导致其他线程乘虚而入;问题的原因在于锁标志和释放锁标志是两个动作;需要让他们一起执行,需要使用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来实现分布式作锁。

posted @ 2023-09-01 17:35  花园SON  阅读(23)  评论(0编辑  收藏  举报