分布式锁

发布时间 2023-03-23 00:14:34作者: or追梦者

参考:https://www.cnblogs.com/wangyingshuo/p/14510524.html

 

介绍

多线程环境下控制对共享资源的访问以保证数据一致性。

特点:跨进程、跨服务、跨服务器   

互斥性 ——任意时刻,只有一个客户持有锁

超时释放——持有锁超时,可以释放,防止死锁

可重入——一个线程获取锁后可以再次请求锁,加几次锁就需要释放几次锁

自动续期——守护线程判断是否需要续期

高可用,高性能——加锁和解锁需要开销尽可能低,同时也要保证高可用

安全性——锁只能被持有的线程删除,不能被其他线程删除

 

 

jvm本地锁有三种情况导致锁失效

多例模式、

事务 读未提交

集群部署

 

 

单机版:lua脚本

setnxex  不存在才会设值并设置过期时间     nx 排他独占    过期时间 防止死锁

问题1:业务超时锁过期自动删除      过期时间稍微设长一点  更好点需要自动续期   

问题2:超时后其他线程进行了加锁,此时业务执行完进行锁删除 ,需要判断是否是自己的锁,以免误删别人的锁     删除锁需要加判断是否是自己的  UUID

=》判断锁和删除锁 需要是原子操作,因为可能还没来得及删除,redis中锁过期,被其他线程占用了锁,此时删除的依然是其他线程的锁

解决:redis官方提供了lua脚本来实现判断并删除锁的原子操作

 

=>可重入  hash + lua脚本

使用hash结构  

设置锁:hsetnx lock uuid+线程id value

判断锁是否存在:hexists  lock  uuid+线程id

重入计数加一:hincrby lock  uuid+线程id

 

hset key field value:hset lock uuid+线程id count (count为重入次数)

使用uuid+线程id标识自己的锁,每个服务生成一个uuid 唯一  , 保证重入时锁是同一个锁

 

加锁:

1.判断锁是否被独占(exists),如果没有,直接获取锁(hset/hincrby)并设置过期时间(expire)

2.如果锁被占用,则判断是否是当前线程占用的,如果是则重入(hincrby)并设置过期时间(expire)

3.否则获取锁失败,重试

 

 

 

 

 

 

 

=》自动续期(定时任务)

子线程处理自动续期 

Timer定时器+lua脚本

判断锁是否自己的  hexists == 1  , 执行expire重置

 

解锁:

直接del会有误删问题

先判断再删除并保证原子操作:lua脚本

hash+lua脚本;可重入:

  1.判断当前线程的锁是否存在,不存在返回nil,将来抛出异常

  2.存在则直接减1(hincrby -1),判断减1后的值是否为0,为0则释放锁(del),并返回1

  3.不为0,则返回0

重试:递归、循环

 

 

 

 

redis方式实现二:redisson

对于锁的自动续期问题提供更好的解决方案

定时守护线程,每个一段时间检查锁是否还存在,存在则对锁的过期时间进行延长,防止锁提前释放

主要是提供看门狗解决自动续期问题

属于单机版方案

 

 

 

线程加锁成功=》启动一个看门狗(守护线程),每隔10s检查一下

  =》如果线程还持有锁,则延长 =》实现自动续期

 

redis实现方式三:Redlock+redisson

redis一般集群部署

如果线程1在master节点拿到锁,但是加锁的key还没有同步到slave节点,恰好这时master发生了故障,一个slave节点就会升级为master节点。

线程2可以获取同一个key的锁,线程1也以为拿到了锁,这样锁就失效了

redis提供分布式锁算法:RedLock

 

核心思想:

搞多个Redis master部署,以保证不会同时宕掉。并且master节点完全相互独立,相互之间不存在数据同步。同时需要确保在这多个master实例上是在与redis单实例使用相同的方法来获取和释放锁