redlock-redis分布式锁的实现

介绍

  Redlock全名叫做Redis-Distributed-Lock。用redis实现的分布式锁。
  单节点的情况下,可以使用setnx实现分布式锁,在集群的情况下,就需要是要redlock来实现分布式锁。


实现

  假设一个集群中有5台机器,现在进行加锁,大概流程如下:

  1. 获取当前系统的时间戳,毫秒为单位。
  2. 依次尝试从5个实例,使用相同的key和具有唯一性的value获取锁。当向redis请求获取锁时,客户端需要设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。这样可以避免在某台实例挂掉的情况下,客户端仍在等待结果。
  3. 客户端使用当前时间减去开始获取锁的时间,就得到了获取锁使用的时间。当从大多数节点获取锁成功,并且获取锁使用的时间小于锁失效时间,才算获取锁成功
  4. 如果成功获取到了锁,锁的真正有效时间等于有效时间减去获取锁使用的时间。
  5. 如果获取锁失败,客户端应该在所有实例上解锁。(没有成功也要解锁,防止实际加锁成功,但因为客户端没有得到响应造成的误判)

特点

关键

  1. 先假设client获取所有实例,所有实例包含相同的key和过期时间(TTL) ,但每个实例set命令时间不同导致不能同时过期,第一个set命令之前是T1,最后一个set命令后为T2,则此client有效获取锁的最小时间为TTL-(T2-T1)-时钟漂移(这里算出来的是实际可用时间)。
  2. 对于以N/2+1(也就是一半以上)的方式判断获取锁成功,是因为如果小于一半判断为成功的话,有可能出现多个client都成功获取锁的情况,从而使锁失效。
  3. 一个client锁定大多数事例耗费的时间大于或接近锁的过期时间,就认为锁无效,并且解锁这个redis实例(不执行业务) ;只要在TTL时间内成功获取一半以上的锁便是有效锁;否则无效。

系统保活

  1. 获取锁失败(或者业务完成),能够自动释放锁,不能等到锁过期。
  2. 在client重试获取锁前(第一次失败到第二次重试时间间隔)大于第一次获取锁消耗的时间。
  3. 重试获取有次数限制。

崩溃恢复

  1. 如果redis没有持久化功能,在clientA获取锁成功后,所有redis重启,clientB能够再次获取到锁,这样违法了锁的排他互斥性。
  2. 如果启动AOF永久化存储,事情会好些。举例:当我们重启redis后,由于redis过期机制是按照unix时间戳走的,所以在重启后,然后会按照规定的时间过期,不影响业务;但是由于AOF同步到磁盘的方式默认是每秒1次,如果在一秒内断电(这一秒的数据没有写到磁盘,锁没有写入磁盘),会导致数据丢失,立即重启会造成锁互斥性失效;但如果同步磁盘方式使用Always(每一个写命令都同步到硬盘)造成性能急剧下降;所以在锁完全有效性和性能方面要有所取舍。
  3. 有效解决既保证锁完全有效性及性能高效及即使断电情况的方法是redis同步到磁盘方式保持默认的每秒,在redis无论因为什么原因停掉后要等待TTL(一个锁有效期)时间后再重启(学名:延迟重启) ;缺点是 在TTL时间内服务相当于暂停状态。

总结

  1. TTL时长要大于正常业务执行的时间+获取所有redis服务消耗时间+时钟漂移。
  2. 获取redis所有服务消耗时间要远小于TTL时间,并且获取成功的锁个数要在总数的一般以上:N/2+1。
  3. 锁重试次数要有限制。
  4. 在redis崩溃后(无论一个还是所有),要延迟TTL时间重启redis。

问题

故障转移导致无法实现真正的redlock?

  因为redis在进行主从复制的时候是异步完成的,比如在clientA获取锁后,主redis复制数据到从redis过程中崩溃了,导致没有复制到从redis中,然后从redis选举出一个升级为主redis,造成新的主redis没有clientA 设置的锁,这是clientB尝试获取锁,并且能够成功获取锁,导致互斥失效。
  这个失败的原因是因为从redis立刻升级为主redis,如果能够过TTL时间再升级为主redis(延迟升级)后,或者立刻升级为主redis但是过TTL的时间后再执行获取锁的任务,就能成功产生互斥效果;是不是这样就能实现基于redis主从的Redlock。这样会产生某一个集群某一时刻没有主服务器。

redlock重试

  当client不能获取锁时,应该在随机时间后重试获取锁;并且最好在同一时刻并发的把set命令发送给所有redis实例;而且对于已经获取锁的client在完成任务后要及时释放锁,这是为了节省时间。

redlock释放锁

  由于释放锁时会判断这个锁的value是不是自己设置的,如果是才删除;所以在释放锁时非常简单,只要向所有实例都发出释放锁的命令,不用考虑能否成功释放锁。

Redisson

  redlock的实际使用模板。

https://www.cnblogs.com/niceyoo/p/13736140.html