复述,实现分布式锁的方法示例

  

之前我们使用的定时任务都是只部署在了单台机器上,为了解决单点的问题,为了保证一个任务,只被一台机器执行,就需要考虑锁的问题,于是就花时间研究了这个问题。到底怎样实现一个分布式锁呢?

  

锁的本质就是互斥,保证任何时候能有一个客户端持有同一个锁,如果考虑使用复述来实现一个分布式锁,最简单的方案就是在实例里面创建一个键值,释放锁的时候,将键值删除。但是一个可靠完善的分布式锁需要考虑的细节比较多,我们就来看看如何写一个正确的分布式锁。

  

<强>单机版分布式锁SETNX

  

所以我们直接基于复述的setNX(如果不存在组)命令,实现一个简单的锁。直接上伪码

  

锁的获取:

        设置resource_name my_random_value NX PX 30000      

锁的释放:

        如果redis.call(“获得”,键[1])==ARGV [1]   返回redis.call(“▽”键[1])   其他的   返回0   之前结束      

几个细节需要注意:

  

首先在获取锁的时候我们需要设置设置超时时间。设置超时时间是为了,防止客户端崩溃,或者网络出现问题以后锁一直被持有。真个系统就死锁了。

  

使用setNX命令,保证查询和写入两个步骤是原子的

  

在锁释放的时候我们判断了键[1])==ARGV[1],在这里键[1]是从复述,里面取出来的价值,ARGV[1]是上文生成的my_random_value。之所以进行以上的判断,是为了保证锁被锁的持有者释放。我们假设不进行这一步校验:

  
      <李>客户端一个获取锁,后发线程挂起了,时间大于锁的过期时间。   <李>锁过期后,客户端B获取锁。   <李>客户端一个恢复以后,处理完相关事件,向复述,发起del命令。锁被释放李   <李>客户端C获取锁。这个时候一个系统中同时两个客户端持有锁。   
  

造成这个问题的关键,在于客户端B持有的锁,被客户端一个释放了。

  

锁的释放必须使用lua脚本,保证操作的原子性。锁的释放包含了,判断,德尔三个步骤。如果不能保证三个步骤的原子性,分布式锁就会有并发问题。

  

注意了以上细节,一个单复述,节点的分布式锁就达成了。

  

在这个分布式锁中还是存在一个单点的复述。也许你会说,复述是主从的架构,发生故障的时候切换到奴隶就好,但是复述的复制是异步的。

  
      <李>如果在客户端在大师上拿到了锁。   <李>在主人将数据同步到奴隶上之前,主人宕机。   <李>客户端B就从奴隶上又一次拿到了锁。   
  

这样由于主人的宕机,造成了同时多人持有锁。如果你的系统可用接受短时时间内,有多人持有锁。这个简单的方案就能解决问题。

  

但是如果解决这个问题.Redis的官方提供了一个Redlock的解决方案。

  

<强> RedLock的实现

  

为了解决,复述,单点的问题.Redis的作者提出了RedLock的解决方案。方案非常的巧妙和简洁。
  RedLock的核心思想就是,同时使用多主来个复述,冗余,且这些节点都是完全的独立的,也不需要对这些节点之间的数据进行同步。

  

假设我们有N个复述,节点,N应该是一个大于2的奇数.RedLock的实现步骤:

  
      <李>取得当前时间李   <李>使用上文提到的方法依次获取N个节点的复述,锁。   <李>如果获取到的锁的数量大于(N/2 + 1)个,且获取的时间小于锁的有效时间(锁有效时间)就认为获取到了一个有效的锁,锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。   <李>如果获取锁的数量小于(N/2 + 1),或者在锁的有效时间(锁有效时间)内没有获取到足够的说,就认为获取锁失败。这个时候需要向所有节点发送释放锁的消息。   
  

对于释放锁的实现就很简单了。想所有的复述,节点发起释放的操作,无论之前是否获取锁成功。

  

同时需要注意几个细节:

  

重试获取锁的间隔时间应当是一个随机范围而非一个固定时间。这样可以防止,多客户端同时一起向复述,集群发送获取锁的操作,避免同时竞争。同时获取相同数量锁的情况。(虽然概率很低)

  

如果某大师节点故障之后,回复的时间间隔应当大于锁的有效时间。

  
      <李>假设有A, B, C三个复述,节点。   <李>客户端foo获取到了A、B两个锁。

    复述,实现分布式锁的方法示例