分布式锁学习

一. 数据库锁

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

二. zookeeper分布式锁

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

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

zookeeper如果长时间没有检测到客户端心跳的时候就会认为Session过期,那么这个Session所创建的所有的临时节点都会被删除
所以网络、jvm gc等原因导致长时间没有收到心跳,会导致多客户端操作共享资源。

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

三. Redis分布式锁

单实例

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

//添加锁
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. 没有高可用。

集群

假设有一 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时间内,对客户端不可用即可,在这个时间内,所有客户端锁将被失效或者自动释放。

四、对比

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

[1] http://zookeeper.apache.org/doc/current/recipes.html#sc_recipes_Locks
[2] Distributed locks with Redis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值