基于Redis的分布式锁

Redis单点方式:html

首先,Redis客户端为了获取锁,向Redis节点发送以下命令:node

SET resource_name my_random_value NX PX 30000

上面的命令若是执行成功,则客户端成功获取到了锁,接下来就能够访问共享资源了;而若是上面的命令执行失败,则说明获取锁失败。redis

注意,在上面的SET命令中:算法

  • my_random_value是由客户端生成的一个随机字符串,它要保证在足够长的一段时间内在全部客户端的全部获取锁的请求中都是惟一的。
  • NX表示只有当resource_name对应的key值不存在的时候才能SET成功。这保证了只有第一个请求的客户端才能得到锁,而其它客户端在锁被释放以前都没法得到锁。
  • PX 30000表示这个锁有一个30秒的自动过时时间。固然,这里30秒只是一个例子,客户端能够选择合适的过时时间。

最后,当客户端完成了对共享资源的操做以后,执行下面的Redis Lua脚原本释放锁数据库

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

这段Lua脚本在执行的时候要把前面的my_random_value做为ARGV[1]的值传进去,把resource_name做为KEYS[1]的值传进去。服务器

总结:dom

1.必须设置过时时间,过时时间须要谨慎考虑,过短锁没等主动释放就过时,太长一旦拥有锁的对象出现问题,别人会很长时间没法使用。异步

2.setnx 和 过时时间必须是原子的分布式

3.value必须是随机的,保证解铃还须系铃人,别人解不了post

4.释放必须用Lua原子方式进行。由于Get和Del是分布进行的,没发指定删除随机value的key,因此必须原子性,防止删除的时候不是本身的锁,而是过时后别人加上去的锁。

另外:单机的问题没法达到高可用,当redis宕了后,锁失效,经过slave方式处理也可能会有1s内的数据同步问题,若是正好遇上master上加上锁,还没同步到slave上,master宕,slave变成master时锁不对。

 

多redis集群方式:(客户端须要作的事情太多,不太好用仍是用zk吧)

  1. 获取当前时间。
  2. 完成获取锁的整个过程(与N个Redis节点交互)。
  3. 再次获取当前时间。
  4. 把两个时间相减,计算获取锁的过程是否消耗了太长时间,致使锁已通过期了。若是没过时,
  5. 客户端持有锁去访问共享资源。

 

考虑:若是Client1 获取lock1后,执行了Full GC,到了过时时间,lock1过时。Client2获取lock2,执行数据操做。Client1恢复运行,执行数据操做!这样两个Client同时拿到了锁。

可让Lock Server在分配锁的时候返回一个递增数字,在执行数据操做的时候,须要提交这个数字,记录最后一次执行的数字,而且只接受比这个数字大的请求。保证请求数据操做的Client是按拿到锁的顺序执行的。但redis暂时不能返回这个数字。

 

使用分布式锁的方式有两种状况:

  • 为了效率(efficiency),协调各个客户端避免作重复的工做。即便锁偶尔失效了,只是可能把某些操做多作一遍而已,不会产生其它的不良后果。好比重复发送了一封一样的email。
  • 为了正确性(correctness)。在任何状况下都不容许锁失效的状况发生,由于一旦发生,就可能意味着数据不一致(inconsistency),数据丢失,文件损坏,或者其它严重的问题。

结论:

  • 若是是为了效率(efficiency)而使用分布式锁,容许锁的偶尔失效,那么使用单Redis节点的锁方案就足够了,简单并且效率高。Redlock则是个太重的实现(heavyweight)。
  • 若是是为了正确性(correctness)在很严肃的场合使用分布式锁,那么不要使用Redlock。它不是创建在异步模型上的一个足够强的算法,它对于系统模型的假设中包含不少危险的成分(对于timing)。并且,它没有一个机制可以提供fencing token。那应该使用什么技术呢?Martin认为,应该考虑相似Zookeeper的方案,或者支持事务的数据库。

 

http://zhangtielei.com/posts/blog-redlock-reasoning.html

 

一个基于ZooKeeper构建分布式锁的描述(固然这不是惟一的方式):

  • 客户端尝试建立一个znode节点,好比/lock。那么第一个客户端就建立成功了,至关于拿到了锁;而其它的客户端会建立失败(znode已存在),获取锁失败。
  • 持有锁的客户端访问共享资源完成后,将znode删掉,这样其它客户端接下来就能来获取锁了。
  • znode应该被建立成ephemeral的。这是znode的一个特性,它保证若是建立znode的那个客户端崩溃了,那么相应的znode会被自动删除。这保证了锁必定会被释放。

看起来这个锁至关完美,没有Redlock过时时间的问题,并且能在须要的时候让锁自动释放。但仔细考察的话,并不尽然。

ZooKeeper是怎么检测出某个客户端已经崩溃了呢?实际上,每一个客户端都与ZooKeeper的某台服务器维护着一个Session,这个Session依赖按期的心跳(heartbeat)来维持。若是ZooKeeper长时间收不到客户端的心跳(这个时间称为Sesion的过时时间),那么它就认为Session过时了,经过这个Session所建立的全部的ephemeral类型的znode节点都会被自动删除。

设想以下的执行序列:

  1. 客户端1建立了znode节点/lock,得到了锁。
  2. 客户端1进入了长时间的GC pause。
  3. 客户端1链接到ZooKeeper的Session过时了。znode节点/lock被自动删除。
  4. 客户端2建立了znode节点/lock,从而得到了锁。
  5. 客户端1从GC pause中恢复过来,它仍然认为本身持有锁。

最后,客户端1和客户端2都认为本身持有了锁,冲突了。这与以前Martin在文章中描述的因为GC pause致使的分布式锁失效的状况相似。

ZooKeeper的watch机制。这个机制能够这样来使用,好比当客户端试图建立/lock的时候,发现它已经存在了,这时候建立失败,但客户端不必定就此对外宣告获取锁失败。客户端能够进入一种等待状态,等待当/lock节点被删除的时候,ZooKeeper经过watch机制通知它,这样它就能够继续完成建立操做(获取锁)。这可让分布式锁在客户端用起来就像一个本地的锁同样:加锁失败就阻塞住,直到获取到锁为止。这样的特性Redlock就没法实现。

小结一下,基于ZooKeeper的锁和基于Redis的锁相比在实现特性上有两个不一样:

  • 在正常状况下,客户端能够持有锁任意长的时间,这能够确保它作完全部须要的资源访问操做以后再释放锁。这避免了基于Redis的锁对于有效时间(lock validity time)到底设置多长的两难问题。实际上,基于ZooKeeper的锁是依靠Session(心跳)来维持锁的持有状态的,而Redis不支持Sesion。
  • 基于ZooKeeper的锁支持在获取锁失败以后等待锁从新释放的事件。这让客户端对锁的使用更加灵活。

http://zhangtielei.com/posts/blog-redlock-reasoning-part2.html

相关文章
相关标签/搜索