分布式锁学习

一. 数据库锁

建立一张锁表,当要锁住某个资源时,就在该表中增长一条记录,想要释放锁的时候就删除这条记录。html

二. zookeeper分布式锁

Zookeeper 的分布式锁是经过临时节点(EPHEMERAL)实现的。当客户端会话终止或超时后 Zookeeper 会自动删除临时节点。 加锁流程: 假设锁空间的根节点为 /_locknode_node

  1. 客户端调用create()在 /_locknode_ 下建立临时有序的子节点,(第一个客户端子节点为/_locknode_/lock-0000000000,第二个为/_locknode_ /lock-0000000001,以此类推);
  2. 客户端调用getChildren(watch=false)获取获取子节点列表,注意wtach设置为false,以免羊群效应(Herd Effect);
  3. 若是在步骤1中建立的路径名具备最低序列号后缀,则客户端具备锁,不然在当前节点前一位的节点调用exist(watch=true),并使用下一个最低序列号。
  4. 若是exists()返回false,请转到步骤2。不然,在转到步骤2以前,请等待上一步中路径名的通知。

zookeeper若是长时间没有检测到客户端心跳的时候就会认为Session过时,那么这个Session所建立的全部的临时节点都会被删除
因此网络、jvm gc等缘由致使长时间没有收到心跳,会致使多客户端操做共享资源。redis

zookeeper在集群模式下,Client发的请求只会由Leader执行,发给Follwer的请求会转发给Leader。Leader接收到请求后会通知Follwer进行投票,Follwer把投票结果发送给Leader,只要半数以上返回了ACK信息就认为经过,则执行 commit ,同时提交本身。再返回给Client。
假设Follwer宕机,是不会转发到Leader,因此不会获取到锁。假设Leader宕机,就不会进行消息广播,会先进行选取新Leader再处理,因此也不会丢失信息。算法

三. Redis分布式锁

单实例

Redis文档中描述了单实例实现分布式锁的正确方法:sql

//添加锁
SET resource_name my_random_value NX PX 30000
复制代码
//释放锁
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
复制代码

添加锁时,key 的值是 my_random_value (一个随机值),释放锁时先经过判断当前 key 的值是否等于指定的值,这样能够避免误删,而且经过lua脚本实现原子性操做
若是不是原子性操做也会致使误删。del 命令若是因网络等缘由延时,这时恰好 key 过时被另外一个客户端获取了共享资源,而后 del 命令又到达 redis 实例致使误删。数据库

单实例有两个缺点,1. 如何设定过时时间,2. 没有高可用。apache

集群

假设有一 Master 一 Slave ,当 Master 节点获取到锁以后,还没同步到 Slave ,Master 就宕机发生主备切换,Slave 晋升为 Master ,此时再申请锁时,就会获取到同一共享资源。服务器

对于这种状况,redis的做者antirez提出了RedLock算法(但依然没有解决设定过时时间的问题),获取大多数节点的锁就算加锁成功,流程以下(来自官方文档):
假设有N个Redis master(文档假设有5个,大多数就是大于等于3),这些节点彻底互相独立,不存在主从复制或者其余集群协调机制网络

  1. 客户端获取当前时间(毫秒)
  2. 按顺序使用相同的 key 和随机值获取全部N个实例中的锁。在步骤2期间,当在每一个实例中设置锁定时,客户端须要设置一个响应超时时间,这个超时时间应该小于锁的失效时间。 例如,若是自动释放时间是10秒,则向每一个节点请求锁的超时时间应该在5-50毫秒之间。这样能够避免服务器端Redis已经挂掉的状况下,客户端还在死死地等待响应结果。若是服务器端没有在规定时间内响应,客户端应该尽快尝试另一个Redis实例。
  3. 客户端计算步骤2所花费的总时间,减去开始获取锁的时间(步骤1记录的时间)就获得获取锁使用的时间。当且仅当从大多数(这里是3个节点)的Redis节点都取到锁,而且使用的时间小于锁失效时间时,锁才算获取成功。
  4. 若是取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
  5. 若是由于某些缘由,获取锁失败(没有在至少N/2+1个Redis实例获取到锁或者获取锁时间已经超过了有效时间),客户端应该在全部的Redis实例上进行解锁(即使某些Redis实例根本就没有加锁成功)。

崩溃恢复 假设有5个节点,如今客户端成功向3个节点加锁,由于大于等于N/2+1个Redis实例,因此加锁成功。这是3个节点中某一个节点宕机重启,则其余客户端有机会向重启节点和另外两个节点加锁。致使共享资源被屡次操做。
对于该状况,做者提出了延迟重启的方案,即当一个Redis 节点重启后,只要它不参与到任意当前活动的锁,为了达到这种效果,咱们只须要将新的 Redis 实例,在一个TTL时间内,对客户端不可用便可,在这个时间内,全部客户端锁将被失效或者自动释放。dom

4、对比

  1. Redis 的读写性能确定比 zookeeper 高。zookeeper 可靠性高。
  2. zookeeper 的 watch 机制,能够通知到下一个操做共享资源的客户端。这样客户端能够一直等待,直到收到通知再去获取资源锁。而 Redis 没有通知机制。

[1] zookeeper.apache.org/doc/current…
[2] Distributed locks with Redis