$ zookeeper实现分布式锁
$ 锁
完全分布式的锁是全局同步的,意味着任何时刻不会有两个客户端认为他们持有同一把锁,这可以用zookeeper来实现,首先定义一个锁节点。
client加锁的操作如下:
- 调用 create( ) 方法创建一个路径名为
_locknode_/lock-
的临时有序节点路径。 - 在锁节点上调用**getChildren( )**方法(不要watch该节点以避免惊群效应)。
- 如果第1步中创建的路径后缀序号是最小的,则client获得了锁并退出后续的协议。
- client在锁目录下比自己序号小一个的路径上调用**exists( )**方法并设置watch标志。
- 如果**exists( )**返回
false
,回到第2步;否则等待第4步中设置的通知然后返回第2步。
client释放锁的协议很简单:只要把第1步中创建的节点删除即可。
需要注意的几点:
移除一个节点只会唤醒一个client,因为每个节点只被一个clientwatch,这样就避免了惊群效应。
不会有轮询或者超时的操作。
由这种实现锁的方式很容易实现查看锁竞争的数量,移除锁,debug锁情况等。
$ 共享锁
进行一些调整就可以实现共享锁:
获取读锁:
- 调用 create( ) 方法创建一个路径名为
_locknode_/read-
的临时有序节点路径。 - 在锁节点上调用**getChildren( )**方法(不要watch该节点以避免惊群效应)。
- 如果没有子节点的路径名以
write-
开头并且序号是第1步中创建节点的下一个,则client获得了锁并退出后续的协议。 - 对锁目录中路径名以
write-
开头并且序号是下一个的节点调用**exists( )**方法并设置watch标志。 - 如果**exists( )**返回
false
,回到第2步;否则等待第4步中设置的通知然后返回第2步。
获取写锁:
- 调用 create( ) 方法创建一个路径名为
_locknode_/write-
的临时有序节点路径。 - 在锁节点上调用**getChildren( )**方法(不要watch该节点以避免惊群效应)。
- 如果第1步中创建的路径后缀序号是最小的,则client获得了锁并退出后续的协议。
- client在锁目录下比自己序号小一个的路径上调用**exists( )**方法并设置watch标志。
- 如果**exists( )**返回
false
,回到第2步;否则等待第4步中设置的通知然后返回第2步。
这似乎显得这个方案创建了一个惊群效应:当序号最小的写节点被删除的时候,所有等待获取读锁的client都会感知到。事实上这是有效的行为:所有等待读的client都应该被释放因为他们获取到锁了。惊群效应指的是只有一个或少部分的机器会处理却触发整个集群。
参考:
http://zookeeper.apache.org/doc/r3.4.9/recipes.html
http://www.importnew.com/27019.html
https://colobu.com/2014/12/12/zookeeper-recipes-by-example-2