分布式锁演变思路(基于Redis)

简易版本

最基本的要求就是保证唯一性。
这里的唯一性是指线程A加锁后,线程B对同一把锁加锁不会产生新的锁数据。
那么redis里面的过程就是:使用redis命令setnx(set if not exist),即只能被一个客户端占坑,如果redis实例存在唯一键,如果再想在该key上设置值,就会被拒绝。业务执行成功后,执行 del命令删除锁。

释放固定锁

如果两个线程在redis中加锁对应的value为同一个值,就有可能出现下面的情况:

  1. 客户端1获取锁成功,设置的value为100。
  2. 客户端1在某个操作上阻塞了很长时间。
  3. 过期时间到了,锁自动释放了。
  4. 客户端2尝试获取锁,并且最终获取成功。
  5. 客户端1从阻塞中恢复过来,释放掉了客户端2持有的锁。

所以,可以设置一个随机字符串(比如用机器id+线程id)作为value,它保证了一个客户端释放的锁必须是自己持有的那个锁。

增加过期时间

上面这个版本存在的问题就是,释放锁需要客户端的操作,如果此时客户端突然挂了,就没有释放锁的操作了,其他客户端想要重新加锁就加不了了,从而导致死锁。所以利用redis的 expire 给锁设置过期时间。

加锁和超时时间的设置需要setnx和expire两个命令来完成操作,如果在setnx和expire两次命令之间,客户端突然挂掉,这时又无法释放锁,且又回到了死锁的问题。可以使用set 扩展命令 set k v ex 5 nx 来实现 或者用 lua脚本在进行多步命令。

执行超时问题

客户端获取锁后设置超时时间为1分钟,但是执行耗费了2分钟,那么加锁1分钟后锁就释放了其他人就能获取到锁。可以使用定时任务进行锁的超时时间的重置 类似的 redisson 的看门狗线程。

锁的可重入问题

可以使用hash表存储,hkey 为定义的锁名称,然后里面的key为机器+线程ID,v为重入次数。加锁的时候会判断 hkey 是否存在,不存在新建。重入的时候判断 hkey 对应的线程 key 是否存在,存在 v + 1。

主从问题

由于单节点redis存在宕机风险,所以一般会采用主从的部署形式。主从模式下,主节点宕机,但是数据没有复制到从节点,则可能出现问题:A 在主节点上锁,主节点挂了,sentinel 重新选举了主节点,则B可能会加锁成功。

红锁

一种实现方式,比如有5个节点,不是主从和集群关系,加锁时,获取到其中半数以上就算成功,失败的需要将之前加锁的删除。
但是这种方案也存在一定问题:

  • 如果节点组中有节点宕机,但是没有把锁数据持久化,可能出现重复加锁
    如:A B C 节点,server1获取到 A B (C给 server2 获取,所以没获取成功) 获取锁成功
    B 宕机重启,但是锁没有持久化 那么重启后 server2可以加锁 B C 就重复获取锁了
    默认情况Redis AOF持久化是每秒写一次磁盘 可以设置每次修改数据都 fsync,但是这样会降低性能,而且即使执行了fsync也有可能丢失数据
    有人提出延迟重启,就是redis服务挂了不重启,而是过上很久才重启
  • 机器之间时间依赖严重,如果redis组中时钟错乱也是会有问题
  • 由于需要在多个节点加锁,加锁时间增加

在代码编写的时候,其实还要考虑很多情况,比如redis连接的释放、连接池、本地的性能等。所以,基于go实现了一套
https://github.com/oDevilo/redislock

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值