什么是分布式锁?

一般分布式锁,通常要满足如下特性:

  • 1)、互斥性:同一时刻多个客户端对共享资源的访问存在互斥性;
  • 2)、防死锁:对锁设置超时时间,防止客户端一直占用着锁,即防止死锁;
  • 3)、可重入性:一个客户端上的同一个线程如果获取锁之后还可以再次获取这个锁(客户端封装,可以使用threadlocal存储持有锁的信息);
  • 4)、持锁人解锁: 客户端自己加的锁自己解除,不能将别人加的锁给删掉了;

实现分布式锁的方式有很多种,常见的有三种方式:

  • 1)、使用数据库方式,将执行的方法名称作为数据库主键,保证唯一;
  • 2)、Redis分布式锁,常见的使用Redisson开源框架;
  • 3)、Zookeeper分布式锁,利用ZK的临时有序节点 + watch监听机制实现;

分布式锁是一种非常有用的技术手段。实现高效的分布式锁有三个属性需要考虑:
● 安全属性:互斥,不管什么时候,只有一个客户端持有锁
● 效率属性A:不会死锁
● 效率属性B:容错,只要大多数redis节点能够正常工作,客户端端都能获取和释放锁。

普通版:单机redis分布式锁
说道Redis分布式锁大部分人都会想到: setnx+lua或者set+lua,加上过期时间
大多都是使用的下面的keyset方法,具体实现过程这里就不再概述。

实现比较轻,大多数时候能满足需求;因为是单机单实例部署,如果redis服务宕机,那么所有需要获取分布式锁的地方均无法获取锁,将全部阻塞,需要做好降级处理。
当锁过期后,执行任务的进程还没有执行完,但是锁因为自动过期已经解锁,可能被其它进程重新加锁,这就造成多个进程同时获取到了锁,这需要额外的方案来解决这种问题。
在集群模式时由于复制延迟,以及主节点挡掉,会造成锁丢失或者解锁延迟现象
事实上这类琐最大的缺点就是它加锁时只作用在一个Redis节点上,即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出 现锁丢失的情况:

在Redis的master节点上拿到了锁;
但是这个加锁的key还没有同步到slave节点;
master故障,发生故障转移,slave节点升级为master节点;
导致锁丢失。
正因为如此,Redis作者antirez基于分布式环境下提出了一种更高级的分布式锁的实现方式:Redlock。笔者认为,Redlock也是Redis所有分布式锁实现方式中唯一能让面试官高潮的方式。

基于单Redis节点的分布式锁的算法就描述完了。这里面有好几个问题需要重点分析一下。

首先第一个问题,这个锁必须要设置一个过期时间。否则的话,当一个客户端获取锁成功之后,假如它崩溃了,或者由于发生了网络分割(network partition)导致它再也无法和Redis节点通信了,那么它就会一直持有这个锁,而其它客户端永远无法获得锁了。antirez在后面的分析中也特别强调了这一点,而且把这个过期时间称为锁的有效时间(lock validity time)。获得锁的客户端必须在这个时间之内完成对共享资源的访问。

第二个问题,第一步获取锁的操作,网上不少文章把它实现成了两个Redis命令:

SETNX resource_name my_random_value
EXPIRE resource_name 30
1
2
虽然这两个命令和前面算法描述中的一个SET命令执行效果相同,但却不是原子的。如果客户端在执行完SETNX后崩溃了,那么就没有机会执行EXPIRE了,导致它一直持有这个锁。

第三个问题,也是antirez指出的,设置一个随机字符串my_random_value是很有必要的,它保证了一个客户端释放的锁必须是自己持有的那个锁。假如获取锁时SET的不是一个随机字符串,而是一个固定值,那么可能会发生下面的执行序列:
客户端1获取锁成功。
客户端1在某个操作上阻塞了很长时间。
过期时间到了,锁自动释放了。
客户端2获取到了对应同一个资源的锁。
客户端1从阻塞中恢复过来,释放掉了客户端2持有的锁。
之后,客户端2在访问共享资源的时候,就没有锁为它提供保护了。

第四个问题,释放锁的操作必须使用Lua脚本来实现。释放锁其实包含三步操作:’GET’、判断和’DEL’,用Lua脚本来实现能保证这三步的原子性。否则,如果把这三步操作放到客户端逻辑中去执行的话,就有可能发生与前面第三个问题类似的执行序列:
客户端1获取锁成功。
客户端1访问共享资源。
客户端1为了释放锁,先执行’GET’操作获取随机字符串的值。
客户端1判断随机字符串的值,与预期的值相等。
客户端1由于某个原因阻塞住了很长时间。
过期时间到了,锁自动释放了。
客户端2获取到了对应同一个资源的锁。
客户端1从阻塞中恢复过来,执行DEL操纵,释放掉了客户端2持有的锁。
实际上,在上述第三个问题和第四个问题的分析中,如果不是客户端阻塞住了,而是出现了大的网络延迟,也有可能导致类似的执行序列发生。

前面的四个问题,只要实现分布式锁的时候加以注意,就都能够被正确处理。但除此之外,antirez还指出了一个问题,是由failover引起的,却是基于单Redis节点的分布式锁无法解决的。正是这个问题催生了Redlock的出现。

RedLock 算法

为了解决Redis集群下Redis分布式锁存在的一些问题,Redis的作者提供了RedLock 算法来解决,能有效防止单点故障。

RedLock 算法的大体流程如下:假设有5个完全独立的redis主服务器

  • 1)、第一步:获取当前时间戳,单位是毫秒;
  • 2)、第二步:轮流用相同的key和value在5个redis节点上请求锁,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点;
  •    比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁。
  • 3)、第三步:客户端通过获取所有节点获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才能说明真正的获取锁成功;
  • 4)、第四步:如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s,这里忽略时钟漂移的时间【实际上还要减去时钟漂移,我们假定没有时钟漂移,忽略不计】;
  • 5)、第五步:如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1),还是因为总消耗时间超过了锁释放时间【超过了TTL】,客户端都会到每个master节点上释放锁,即便是那些没有获取锁成功的节点上,也执行释放锁操作;

算法示意图如下:

上述就是RedLock算法的整体流程,能够在一定程序上解决Redis单点故障的问题,但是RedLock算法也有几个需要注意的地方:

【a】失败重试

当客户端获取锁失败后,应该在随机时间后重试获取锁,并设置一个最大重试次数;

并且最好在同一时刻并发的把set命令发送给所有redis实例,而且对于已经获取锁的client在完成任务后要及时释放锁;

【b】锁的真正有效时间计算方式是:TTL - (T2 - T1) - 时钟漂移

  • TTL指的是设置的锁的总超时时间;
  • T2指的是最后一次获取锁的时间戳;
  • T1指的是第一次获取锁的时间戳;

【c】多个客户端同时加锁的问题

举个例子,有三个客户端A,B,C, 客户端A在redis1、redis2上面加锁成功,客户端B在redis3、redis4加锁成功,客户端C在redis5加锁成功。想一下,这时候,没有一个客户端超过【N/2+ 1】半数以上,这时候所有客户端一直在尝试加锁,但是全部都尝试获取锁失败,这其实就产生了问题。所以建议多个客户端同时加锁的时候,可以再加上一点随机值,尽可能区分一下先后顺序;

【d】Redis节点崩溃恢复问题

同样举个例子,有三个客户端A、B,C,客户端A在redis1、redis2、redis3上加锁成功,说明客户端A获取锁成功,注意这时候如果锁的信息还没同步到redis4、redis5节点上的时候,redis3节点重启了,刚好客户端B在redis3、redis4、redis5中加锁成功,这时候客户端A和客户端B都拿到锁,违反了互斥性。

聪明的小伙伴肯定一下子想到,redis3节点开启持久化应该就可以了啊,开启了持久化机制确实可以,比如aof持久化方式有always实时同步、every second每秒同步策略:

  • always实时同步:导致性能急剧下降;
  • every second每秒同步:如果在一秒内断电,会导致数据丢失,立即重启会造成锁互斥性失效;

所以必须这种考虑,比较好的方式就是:

  • redis同步到磁盘方式保持默认的每秒,然后redis宕机之后要等待TTL时间后再重启,即等锁过期了再重启redis,保证不会出现同时两个客户端拿到锁,保证互斥性【延迟重启】;

同单节点Redis分布式锁一样,常见的RedLock算法也可以借助Redisson实现,感兴趣的小伙伴可以去搜哈一下。以上就是关于RedLock红锁算法的一些学习总结,希望对大家有所帮助。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值