Redis实现分布式锁

 Redis 属于分布式系统,当有多个客户端需要争抢锁时,我们必须要保证,这把锁
不能是某个客户端本地的锁

我自己理解的分布式锁和这个你这个线程锁的区别

你这个线程锁呢主要是为了你多个线程操控一个资源的时候,避免你的那个数据的可靠性。

分布式锁一方面呢,也有保证这个数据可靠性的,另一方面呢,就是你这个假如说多个redis实例里有多个一样的定时任务的话,如果没有分布式锁的话,他就会执行多次,会增加你这个性能开销。

分布式锁的两个要求

要求一:分布式锁的加锁和释放锁的过程,涉及多个操作。所以,在实现分布式锁时,
我们需要保证这些锁操作的原子性


要求二:共享存储系统保存了锁变量,如果共享存储系统发生故障或宕机,那么客户端
也就无法进行锁操作了。在实现分布式锁时,我们需要考虑保证共享存储系统的可靠
性,进而保证锁的可靠性

基于单个 Redis 节点实现分布式锁

 可以看到,Redis 可以使用一个键值对 lock_key:0 来保存锁变量,其中,键是 lock_key,
也是锁变量的名称,锁变量的初始值是 0

我们再来分析下加锁操作。

在图中,客户端 A 和 C 同时请求加锁。因为 Redis 使用单线程处理请求,所以,即使客户
端 A 和 C 同时把加锁请求发给了 Redis,Redis 也会串行处理它们的请求。
我们假设 Redis 先处理客户端 A 的请求,读取 lock_key 的值,发现 lock_key 为 0,所
以,Redis 就把 lock_key 的 value 置为 1,表示已经加锁了。紧接着,Redis 处理客户端
C 的请求,此时,Redis 会发现 lock_key 的值已经为 1 了,所以就返回加锁失败的信息。

刚刚说的是加锁的操作,那释放锁该怎么操作呢?其实,释放锁就是直接把锁变量值设置
为 0。

Redis 可以用哪些单命令操作实现加锁操作。

首先是 SETNX 命令,它用于设置键值对的值。具体来说,就是这个命令在执行时会判断键
值对是否存在,如果不存在,就设置键值对的值,如果存在,就不做任何设置。
举个例子,如果执行下面的命令时,key 不存在,那么 key 会被创建,并且值会被设置为
value;如果 key 已经存在,SETNX 不做任何赋值操作。

对于释放锁操作来说,我们可以在执行完业务逻辑后,使用 DEL 命令删除锁变量。不过,
你不用担心锁变量被删除后,其他客户端无法请求加锁了。因为 SETNX 命令在执行时,如
果要设置的键值对(也就是锁变量)不存在,SETNX 命令会先创建键值对,然后设置它的
值。所以,释放锁之后,再有客户端请求加锁时,SETNX 命令会创建保存锁变量的键值
对,并设置锁变量的值,完成加锁。

不过,使用 SETNX 和 DEL 命令组合实现分布锁,存在两个潜在的风险。

第一个风险是,假如某个客户端在执行了 SETNX 命令、加锁之后,紧接着却在操作共享数
据时发生了异常,结果一直没有执行最后的 DEL 命令释放锁。因此,锁就一直被这个客户
端持有,其它客户端无法拿到锁,也无法访问共享数据和执行后续操作,这会给业务应用
带来影响。解决方案:给锁加一个过期时间。或者使用lua脚本释放锁。

我们再来看第二个风险。如果客户端 A 执行了 SETNX 命令加锁后,假设客户端 B 执行了
DEL 命令释放锁,此时,客户端 A 的锁就被误释放了。如果客户端 C 正好也在申请加锁,
就可以成功获得锁,进而开始操作共享数据。这样一来,客户端 A 和 C 同时在对共享数据
进行操作,数据就会被修改错误,这也是业务层不能接受的

解决方案:因为setnx设置 0 1 无法判断是哪个客户端操作的,那么就给锁设置一个唯一标识,只有判断锁的这个唯一标识是你的,才能删除锁。

基于多个 Redis 节点实现高可靠的分布式锁

为了避免 Redis 实例故障而导致的锁无法工作的问题,分布式锁算法 Redlock。

Redlock 算法的基本思路,是让客户端和多个独立的 Redis 实例依次请求加锁,如果客户
端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布
式锁了,否则加锁失败。这样一来,即使有单个 Redis 实例发生故障,因为锁变量在其它
实例上也有保存,所以,客户端仍然可以正常地进行锁操作,锁变量并不会丢失

我们来具体看下 Redlock 算法的执行步骤。Redlock 算法的实现需要有 N 个独立的 Redis
实例。

第一步是,客户端获取当前时间。


第二步是,客户端按顺序依次向 N 个 Redis 实例执行加锁操作

这里的加锁操作和在单实例上执行的加锁操作一样,使用 SET 命令,带上 NX,EX/PX 选
项,以及带上客户端的唯一标识。当然,如果某个 Redis 实例发生故障了,为了保证在这
种情况下,Redlock 算法能够继续运行,我们需要给加锁操作设置一个超时时间。
如果客户端在和一个 Redis 实例请求加锁时,一直到超时都没有成功,那么此时,客户端
会和下一个 Redis 实例继续请求加锁。加锁操作的超时时间需要远远地小于锁的有效时
间,一般也就是设置为几十毫秒

第三步是,一旦客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过
程的总耗时。

客户端只有在满足下面的这两个条件时,才能认为是加锁成功。

条件一:客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁;
条件二:客户端获取锁的总耗时没有超过锁的有效时间

在满足了这两个条件后,我们需要重新计算这把锁的有效时间,计算的结果是锁的最初有
效时间减去客户端为获取锁的总耗时。如果锁的有效时间已经来不及完成共享数据的操作
了,我们可以释放锁,以免出现还没完成数据操作,锁就过期了的情况。

在 Redlock 算法中,释放锁的操作和在单实例上释放锁的操作一样,只要执行释放锁的
Lua 脚本就可以了。

这里可以这莫理解多个redis来保存锁,其实一般这些申请加锁操作的这些数据库,是不需要保存业务数据的,只存锁。其实说白了就但拎出几台机器来让你尽享枷锁操作,满足上述两个条件,然后你这个客户端才占有锁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值