首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

利用redis实现分布式锁

一、基于单个redis节点实现的分布式锁

因为redis是单线程程序,可以天然的保证线程安全,只要我们的命令是单条命令,就可以保证操作的安全性,而redis中给我们提供了setnx key value命令,setnx命令的作用就是当我们的redis中没有这个key的键值队时,就会创建这个键值队的值,如果已经有了这个key就不作操作;所以我们可以使用setnx命令来实现我们的分布式加锁操作。但setnx的命令会存在风险

setnx没有给锁设置过期时间,一旦客户端故障没有进行释放锁操作,就将导致没有办法在获得锁。

所以当我们需要给锁设置过期时间时,redis又给我们提供了另外一个命令:

SET key value [EX seconds | PX milliseconds] [NX]

实例:SET lock_key unique_value NX PX 10000

其中:

key可以是任意的值,为了能在分布式使用者之间保持一致,直接以共享资源命名更好

value保证每个使用者唯一性即可,便于锁的释放

是redis的语义,即表示这个key不存在的时候才能set成功,保证了不同使用者的互斥性

EX | PX 表示数据的有效时间,两者的主要区别是时间单位不同。

为什么我们的value要保证使用者的唯一性呢?主要就是因为当我们在释放锁也就是del key时,需要保证只有真正获得锁的程序才能去释放对应的锁,而不是谁都可以进行锁的释放,防止自己获得锁,被别人释放,因此我们释放锁的步骤就变成了:

先去判断锁变量的值,是否是自己获得锁。

如果是自己获取的锁,就可以进行锁释放。

如果不是自己获取的锁,就无法进行锁释放。

所以当我们进行释放锁时,就不只是一个命令了,那么在redis中为了保证多个命令的原子性,我们就需要编写lua脚本去进行锁的释放,释放锁的脚本如下:

其中:这个脚本的语义就是当输入的keys[1]在redis的值等于输入的argv[1]时,删除这个原有的key,即释放锁;不等则无法释放锁;keys[1]:获取锁时输入的key,即资源名;argv[1]:即是获取锁输入时的value,这个value的唯一性决定了使用者只能删除自身已经获取的锁,不会误删到别人的。

但是基于单个节点的redis锁无法存在单点故障,并且无法保证锁的高可用性,就算是主从结构,当主节点发生故障;而从节点还有没有锁的信息值时,我们的锁也就丢失了;所以为了解决上述问题;redis作者提供了一个基于多个redis节点实现的锁,也就是Redlock。

二、基于多个redis节点实现分布式锁

Redlock的基本思路就是:让客户端分别和多个redis实例依次进行加锁操作,如果客户端能够获取半数以上的服务端的加锁成功操作,那么就认为客户端获取了分布式锁。其加锁步骤可以分为三个步骤:

客户端获取当前时间

客户端按顺序依次与redis服务端进行加锁操作(加锁操作,就是和单个redis节点一样使用 set 命令加上选项 nx 和过期时间px/ex,同时为了保证不会单个redis实例宕机导致无法正常获取分布式锁,需要给加锁操作设置一个超时时间)

客户端完成和所有实例的加锁操作后,客户端需要进行计算整个加锁的总耗时时间。并且需要满足两个条件,才算加锁成功:

超过半数的redis服务器加锁成功(大于N/(2 + 1))

加锁的总耗时小于锁的过期时间

如果加锁操作,没有成功完成,redis客户端,需要向所有服务器发送删除锁命令操作。

使用了redlock就能保证我们锁的安全性了?针对redlock我们可以设定一些场景去看一下存在的问题:

场景一:

假设N=5,A,B,C,D,E

1.使用者1成功锁住了大多数(假设是A,B,C。没有成功的是D,E),使用者1获取锁成功。

2.大多数中有一个节点宕机(假设是C节点),并且该节点并没有持久化使用者1的锁。

3.节点C重启了,使用者2这个时候获取到了大多数(C、D、E)

4.出现了使用者1,使用者2同时获取到了锁,对共享资源操作的互斥性被破坏了。

场景二:

假设N=5,A,B,C,D,E

1.使用者1成功锁住了大多数(假设是A,B,C。没有成功的是D,E),使用者1获取锁成功。

2.大多数中有一个节点出现了时钟跳跃(假设是C节点出现了时钟跳跃到了锁失效时间之后,导致锁过期)

3.使用者2这个时候获取到了大多数(C、D、E)

4.出现了使用者1,使用者2同时获取到了锁,对共享资源操作的互斥性被破坏了

场景三:

假设N=5,A,B,C,D,E

1.使用者1成功锁住了大多数,获取锁成功

2.使用者1开始对共享资源进行操作

3.异常发生:使用者1从开始操作共享资源到释放锁这段时间已经超过了实际锁的有效时间,但是使用者1没有感知

4.异常发生期间使用2获取到了锁,并开始对锁进行操作

5.出现了使用者1和使用者2同时操作共享资源,互斥性被破坏了

上面出现的场景就说明了redlock也并不100%是保证锁的安全的,一旦发生时钟跳跃或者获取锁的资源因为某些原因导致超过了锁的有效时间,就会导致redlock失效。

对应redis实现分布式锁的建议就是:对于不需要保证锁的强安全性的时候,我们直接使用redis单节点的分布式锁就可以(如:邮件系统,多发送一份重复的邮件并不致命);对于Redlock毕竟是基于客户端的分布式锁,在实现上有一定的难度,需要客户端自己协调整个锁机制;而类似zookeeper,etcd,chubby这些,集群直接通过更严格的分布式协议zab,raft,或者paxos内部协调整个锁资源的一致性。对使用者而言只有一个操作入口,会更友好些。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20201115A07ZL000?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券