分布式锁的redis缓存使用方式

基于缓存的分布式锁(公司大牛内部文章分享)
目前有不少成熟的缓存产品,包括Redis,memcached等。这里以Redis为例来分析下使用缓存实现分布式锁的方案。主要的实现方式是使用Jedis.setNX方法来实现。java

public boolean trylock(String key) {    
  ResultCode code = jedis.setNX(key, "This is a Lock.");    
  if (ResultCode.SUCCESS.equals(code))        
    return true;    
  else        
    return false; 
  } 
public boolean unlock(String key){
  ldbTairManager.invalid(NAMESPACE, key); 
}

以上实现方式一样存在几个问题:
一、单点问题。
二、这把锁没有失效时间,一旦解锁操做失败,就会致使锁记录一直在redis中,其余线程没法再得到到锁。
三、这把锁只能是非阻塞的,不管成功仍是失败都直接返回。
四、这把锁是非重入的,一个线程得到锁以后,在释放锁以前,没法再次得到该锁,由于使用到的key在redis中已经存在。没法再执行setNX操做。
五、这把锁是非公平的,全部等待的线程同时去发起setNX操做,运气好的线程能获取锁。redis

固然,一样有方式能够解决。
如今主流的缓存服务都支持集群部署,经过集群来解决单点问题。
没有失效时间?redis的setExpire方法支持传入失效时间,到达时间以后数据会自动删除。
非阻塞?while重复执行。
非可重入?在一个线程获取到锁以后,把当前主机信息和线程信息保存起来,下次再获取以前先检查本身是否是当前锁的拥有者。
非公平?在线程获取锁以前先把全部等待的线程放入一个队列中,而后按先进先出原则获取锁。算法

redis集群的同步策略是须要时间的,有可能A线程setNX成功后拿到锁,可是这个值尚未更新到B线程执行setNX的这台服务器,那就会产生并发问题。
redis的做者Salvatore Sanfilippo,提出了Redlock算法,该算法实现了比单一节点更安全、可靠的分布式锁管理(DLM)。
Redlock算法假设有N个redis节点,这些节点互相独立,通常设置为N=5,这N个节点运行在不一样的机器上以保持物理层面的独立。数据库

算法的步骤以下:
一、客户端获取当前时间,以毫秒为单位。
二、客户端尝试获取N个节点的锁,(每一个节点获取锁的方式和前面说的缓存锁同样),N个节点以相同的key和value获取锁。客户端须要设置接口访问超时,接口超时时间须要远远小于锁超时时间,好比锁自动释放的时间是10s,那么接口超时大概设置5-50ms。这样能够在有redis节点宕机后,访问该节点时能尽快超时,而减少锁的正常使用。
三、客户端计算在得到锁的时候花费了多少时间,方法是用当前时间减去在步骤一获取的时间,只有客户端得到了超过3个节点的锁,并且获取锁的时间小于锁的超时时间,客户端才得到了分布式锁。
四、客户端获取的锁的时间为设置的锁超时时间减去步骤三计算出的获取锁花费时间。
五、若是客户端获取锁失败了,客户端会依次删除全部的锁。
使用Redlock算法,能够保证在挂掉最多2个节点的时候,分布式锁服务仍然能工做,这相比以前的数据库锁和缓存锁大大提后端

问题一:一、GC等场景可能随时发生,并致使在客户端获取了锁,在处理中超时,致使另外的客户端获取了锁?缓存

在一个客户端获取了分布式锁后,在客户端的处理过程当中,可能出现锁超时释放的状况,这里说的处理中除了GC等非抗力外,程序流程未处理完也是可能发生的。以前在说到数据库锁设置的超时时间2分钟,若是出现某个任务占用某个订单锁超过2分钟,那么另外一个交易中心就能够得到这把订单锁,从而两个交易中心同时处理同一个订单。正常状况,任务固然秒级处理完成,但是有时候,加入某个rpc请求设置的超时时间过长,一个任务中有多个这样的超时请求,那么,极可能就出现超过自动解锁时间了。当初咱们的交易模块是用C++写的,不存在GC,若是用java写,中间还可能出现Full GC,那么锁超时解锁后,本身客户端没法感知,是件很是严重的事情。我以为这不是锁自己的问题,上面说到的任何一个分布式锁,只要自带了超时释放的特性,都会出现这样的问题。若是使用锁的超时功能,那么客户端必定得设置获取锁超时后,采起相应的处理,而不是继续处理共享资源。Redlock的算法,在客户端获取锁后,会返回客户端能占用的锁时间,客户端必须处理该时间,让任务在超过该时间后中止下来。安全

问题二:算法依赖本地时间,会出现时钟不许,致使2个客户端同时得到锁的状况?
Redlock有个关键的特性是,获取锁的时间是锁默认超时的总时间减去获取锁所花费的时间,这样客户端处理的时间就是一个相对时间,就跟本地时间无关了。服务器

总结:
由此看来,Redlock的正确性是能获得很好的保证的。仔细分析Redlock,相比于一个节点的redis,Redlock提供的最主要的特性是可靠性更高,这在有些场景下是很重要的特性。可是我以为Redlock为了实现可靠性,却花费了过大的代价。并发

首先必须部署5个节点才能让Redlock的可靠性更强。而后须要请求5个节点才能获取到锁,经过java Future的方式,先并发向5个节点请求,再一块儿得到响应结果,能缩短响应时间,不过仍是比单节点redis锁要耗费更多时间。而后因为必须获取到5个节点中的3个以上,因此可能出现获取锁冲突,即你们都得到了1-2把锁,结果谁也不能获取到锁,这个问题,redis做者借鉴了raft算法的精髓,经过冲突后在随机时间开始,能够大大下降冲突时间,可是这问题并不能很好的避免,特别是在第一次获取锁的时候,因此获取锁的时间成本增长了。若是5个节点有2个宕机,此时锁的可用性会极大下降,首先必须等待这两个宕机节点的结果超时才能返回,另外只有3个节点,客户端必须获取到这所有3个节点的锁才能拥有锁,难度也加大了。分析了这么多缘由,我以为Redlock的问题,最关键的一点在于Redlock须要客户端去保证写入的一致性,后端5个节点彻底独立,全部的客户端都得操做这5个节点。
若是5个节点有一个leader,客户端只要从leader获取锁,其余节点能同步leader的数据,这样,分区、超时、冲突等问题都不会存在。因此为了保证分布式锁的正确性,我以为使用强一致性的分布式协调服务能更好的解决问题。分布式

问题又来了,失效时间我设置多长时间为好?如何设置的失效时间过短,方法没等执行完,锁就自动释放了,那么就会产生并发问题。若是设置的时间太长,其余获取锁的线程就可能要平白的多等一段时间。

对于这个问题目前主流的作法是每得到一个锁时,只设置一个很短的超时时间,同时起一个线程在每次快要到超时时间时去刷新锁的超时时间。在释放锁的同时结束这个线程。如redis官方的分布式锁组件redisson,就是用的这种方案

相关文章
相关标签/搜索