分布式锁的几种实现 (2)

redis cluster 模式下,edis的作者提出可依据RedLock算法:
这个算法的意思大概是这样的:假设redis的部署模式是redis cluster,总共有6个master节点,通过以下步骤获取一把锁:

1.获取当前时间戳,单位是毫秒;

2.轮流尝试在每个master节点上创建锁,过期时间设置较短,一般就几十毫秒;

3.尝试在大多数节点上建立一个锁,比如5个节点就要求是3个节点(n / 2+1);

4.客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了;

5.要是锁建立失败了,那么就依次删除这个锁;

6.只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁。

但是这样的这种算法还是颇具争议的,可能还会存在不少的问题,无法保证加锁的过程一定正确。

基于Redission的实现:

Javaer都知道Jedis,Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持。Redission也是Redis的客户端,相比于Jedis功能简单。Jedis简单使用阻塞的I/O和redis交互,Redission通过Netty支持非阻塞I/O。

Redission封装了锁的实现,其继承了java.util.concurrent.locks.Lock的接口,让我们像操作我们的本地Lock一样去操作Redission的Lock,下面介绍一下其如何实现分布式锁。

如果自己写代码来通过redis设置一个值,是通过下面这个命令设置的。

SET d_lock unique_value NX PX 30000

这里设置的超时时间是30s,假如我超过30s都还没有完成业务逻辑的情况下,key会过期,其他线程有可能会获取到锁。这样一来的话,第一个线程还没执行完业务逻辑,第二个线程进来了也会出现线程安全问题。所以我们还需要额外的去维护这个过期时间,太麻烦了~我们来看看redisson是怎么实现的:

Config config = new Config(); config.useClusterServers() .addNodeAddress("redis://192.168.1.101:7001") .addNodeAddress("redis://192.168.1.101:7002") .addNodeAddress("redis://192.168.1.101:7003") .addNodeAddress("redis://192.168.1.102:7001") .addNodeAddress("redis://192.168.1.102:7002") .addNodeAddress("redis://192.168.1.102:7003"); RedissonClient redisson = Redisson.create(config); RLock lock = redisson.getLock("d_lock"); lock.lock(); lock.unlock();

我们只需要通过它的api中的lock和unlock即可完成分布式锁,他帮我们考虑了很多细节:

redisson所有指令都通过lua脚本执行,redis支持lua脚本原子性执行

redisson设置一个key的默认过期时间为30s,如果某个客户端持有一个锁超过了30s怎么办?

redisson中有一个watchdog的概念,翻译过来就是看门狗,它会在你获取锁之后,每隔10秒帮你把key的超时时间设为30s

这样的话,就算一直持有锁也不会出现key过期了,其他线程获取到锁的问题了。

redisson的“看门狗”逻辑保证了没有死锁发生

(如果机器宕机了,看门狗也就没了。此时就不会延长key的过期时间,到了30s之后就会自动过期了,其他线程可以获取到锁)

看门狗.png

Redis小结
优点: 对于Redis实现简单,性能对比ZK和Mysql较好。如果不需要特别复杂的要求,那么自己就可以利用setNx进行实现,如果自己需要复杂的需求的话那么可以利用或者借鉴Redission。对于一些要求比较严格的场景来说的话可以使用RedLock。

缺点: 需要维护Redis集群,如果要实现RedLock那么需要维护更多的集群。

2.2 Zookeeper 实现

zk实现分布式锁的落地方案:

使用zk的临时节点和有序节点,每个线程获取锁就是在zk创建一个临时有序的节点,比如在/lock/目录下。

创建节点成功后,获取/lock目录下的所有临时节点,再判断当前线程创建的节点是否是所有的节点的序号最小的节点

如果当前线程创建的节点是所有节点序号最小的节点,则认为获取锁成功

如果当前线程创建的节点不是所有节点序号最小的节点,则对节点序号的前一个节点添加一个事件监听。

比如当前线程获取到的节点序号为/lock/003,然后所有的节点列表为:

[/lock/001,/lock/002,/lock/003],则对/lock/002这个节点添加一个事件监听器。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgdxyg.html