Redis(四):Redis如何实现分布式锁?

实现分布式锁的方式有Redis分布式锁、MySQL分布式锁、ZooKeeper分布式锁,Redis分布式锁的本质就是在Redis中“占坑”。

实现一个分布式锁需要满足哪些条件呢?

  1. 互斥性:在任何时刻都只能有一个客户端获得锁;
  2. 健壮性:即使某个客户端在获得锁期间故障而没有释放锁,也要保住后续其他客户端可以获得锁;
  3. 高可用:只要大部分节点正常,客户端就可以获得锁和释放锁;
  4. 唯一性:加锁和解锁必须是同一个客户端;

如何实现Redis分布式锁?

如何加锁?

1. setnx + del

setnx key value # 如果key不存在,则set;如果key存在,则set失败
del key     # 删除key

setnxset if not exist的缩写,通过set key获得锁,使用完后通过del命令释放锁。

如果某个客户端获得锁期间故障了del命令没有被调用怎么办?

2. setnx + expire

setnx key value # 如果key不存在,则set;如果key存在,则set失败
expire key 5    # 设置key过期时间
del key     # 删除key

使用setnx命令set key之后给key设置个过期时间,这样即使客户端在获得锁期间异常也可以保证锁会在过期时间到来时自动释放锁。

但是setnx + expire是两个操作,不是原子性的,如果在setnxexpire期间客户端异常了,还是会导致锁不会被释放,如何解决呢?

expire是依赖于setnx的执行结果,如果setnx没抢到锁,expire是不应该执行的。事务里没有if-else分支逻辑,要么全部执行要么一个都不执行,所以此处不能使用Redis事务解决。

3. set扩展参数

set key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]    #setkey并设置过期时间
del key     # 删除key
  • EX seconds:设置key的过期时间为seconds秒;
  • PX milliseconds:设置key的过期时间为milliseconds毫秒;
  • NX:只有key不存在才会进行set操作;
  • XX:只有key已存在才会对key进行操作;

set命令加上扩展参数,使得setnx + expire操作能一起执行。如果客户端在获得锁期间故障,那么也将会在超时时间到来时释放锁。

4. Redisson

setnx + lua加锁只作用在一个Redis节点上。

在分布式情况下,客户端A在主节点set key成功获得锁,主节点还未将锁同步到从节点,主节点就故障了,此时会选举一个从节点升级为新的主节点,但是这个新的主节点并没有存在客户端A获取成功的这个锁,此时客户端B请求获得锁成功,导致系统中同时有两个客户端持有锁,如何解决呢?

RedLock算法 加锁时,它会向集群中其他主节点发送set(key, value, nx=True, ex=xxx)命令,只要超过n/2 + 1个节点 set 成功,那就认为加锁成功。释放锁时,需要向所有节点发送del指令。

Redisson提供了对RedLock算法的封装,使用Lua脚本实现原子性的加锁和释放锁操作。

如何释放锁?

1. del

客户端加锁是set一个key,那释放锁直接使用del命令将该key删除即可。

假设线程A在获得锁操作期间,锁超时释放了,此时线程B获得了这个锁,当线程A操作完成时进行了一个del操作将锁释放了,线程B操作完成去释放锁时发现锁不存在或者也释放了别的线程的锁,怎么办呢?

2. get + del

set key的时候,将value设置为一个唯一的客户端标识或UUID,客户端在释放锁时,先get keyvalue校验加锁线程是否为自己,如果是,则释放锁。

但是get + del操作并不是原子性的,还是有线程安全问题,怎么解决呢?

3. Lua脚本

使用Lua脚本通过eval/evalsha命令执行get + del操作,Lua脚本保证原子性操作。

猜你喜欢

转载自blog.csdn.net/m0_46757769/article/details/108078635