分布式锁知识点

1、基于redis的最简单的分布式锁

// 获取锁
// NX是指如果key不存在就成功,key存在返回false,PX可以指定过期时间
SET anyLock unique_value NX PX 
30000
// 释放锁:通过执行一段lua脚本
// 释放锁涉及到两条指令,这两条指令不是原子性的
// 需要用到redis的lua脚本支持特性,redis执行lua脚本是原子性的
if
  redis.call("get",KEYS[1])== ARGV[1] then   
  return redis.call("del",KEYS[1])
else   
  return 0
end

 

2、RedLock

Redis分布式锁的缺点在于:如果是单机部署,会存在单机故障问题;

如果是采用master-slave模式,即便通过sentinel做了高可用,如果master节点故障发生主从切换,也可能出现所丢失问题。

因此redis的作者提出了RedLock算法,大意是:

1)获取当前时间戳

2)client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。

   比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁

3)client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功

4)如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移);

5)如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁。

在最后释放锁的时候,antirez在算法描述中特别强调,客户端应该向所有Redis节点发起释放锁的操作。也就是说,即使当时向某个节点获取锁没有成功,在释放锁的时候也不应该漏掉这个节点。可能这个节点也成功执行了SET操作,但是它返回给客户端的响应包却丢失了。

 

redLock存在的问题:
1)安全性问题:

假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:

  1. 客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)。
  2. 节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。
  3. 节点C重启后,客户端2锁住了C, D, E,获取锁成功。

这样,客户端1和客户端2同时获得了锁(针对同一资源)。

解决:延时重启:一个节点崩溃后,先不立即重启它,而是等待一段时间再重启,这段时间应该大于锁的有效时间(lock validity time)

2)客户端长时间阻塞,导致获得的锁释放,访问的共享资源不受保护的问题。

3)在Redlock的算法中,我们可以看到第3步,当获取锁耗时太多,留给客户端的访问共享资源的时间很短,这种情况若来不及操作,是不是要释放锁呢?且到底剩下多少时间才算短?这又是一个选择难题。

4)Redlock算法对时钟依赖性太强,若N个节点中的某个节点发生时间跳跃,也可能会引此而引发锁安全性问题

 

3、Redisson

redisson是一个企业级的开源Redis Client,提供了分布式锁的支持。

Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://192.168.31.101:7001")
.addNodeAddress("redis://192.168.31.101:7002")
.addNodeAddress("redis://192.168.31.101:7003")
.addNodeAddress("redis://192.168.31.102:7001")
.addNodeAddress("redis://192.168.31.102:7002")
.addNodeAddress("redis://192.168.31.102:7003");

RedissonClient redisson = Redisson.create(config);

RLock lock = redisson.getLock("anyLock");
lock.lock();
lock.unlock();
  • redisson所有指令都通过lua脚本执行,redis支持lua脚本原子性执行;
  • redisson通过watchdog实现续期
  • redisson提供了对redlock算法的支持
RedissonClient     redisson    = Redisson.create(config);
RLock lock1 = redisson.getFairLock("lock1");
RLock lock2 = redisson.getFairLock("lock2");
RLock lock3 = redisson.getFairLock("lock3");
RedissonRedLock     multiLock = new RedissonRedLock(lock1,lock2,lock3);
multiLock.lock();
multiLock.unlock();

 

4、基于zookeeper实现分布式锁

1)使用zk的临时节点和有序节点,每个线程获取锁就是在zk创建一个临时有序的节点;

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

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

4)如果不是序号最小的节点,则对节点序号的前一个节点添加一个事件监听。

 znode 特性:

  • 有序节点:假如当前有一个父节点为 /lock,我们可以在这个父节点下面创建子节点;zookeeper 提供了一个可选的有序特性,例如我们可以创建子节点 “/lock/node-” 并且指明有序,那么 zookeeper 在生成子节点时会根据 当前的子节点数量自动添加整数序号。也就是说,如果是第一个创建的子节点,那么生成的子节点为 /lock/node-0000000000,下一个节点则为 /lock/node-0000000001,依次类推。

  • 临时节点:客户端可以建立一个临时节点,在会话结束或者会话超时后,zookeeper 会自动删除该节点。

  • 事件监听:在读取数据时,我们可以同时对节点设置事件监听,当节点数据或结构变化时,zookeeper 会通知客户端。当前 zookeeper 有如下四种事件:

    • 节点创建

    • 节点删除

    • 节点数据修改

    • 子节点变更

 

5、redis和zk方案的优缺点

redis方案:

1)锁不是强一致性;

2)需要自己不断尝试获取锁,比较消耗性能;

3)支持高并发的获取、释放锁。

zk方案:

1)天生定位就是分布式协调,具有强一致性,模型健壮;

2)获取不到锁只要添加一个监听器就可以,不用一直轮询;

3)较多客户端频繁的申请加锁、释放锁,对于zk集群压力比较大。

有zk集群的条件下优先选用zk实现。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值