锁
全局式分布式锁要求任何时刻没有两个客户端会获得同一个锁对象,这可以通过使用ZooKeeper实现。像优先级队列一样,首先需要定义一个锁节点。
在ZooKepeer的发布中src/recipes/lock(https://github.com/apache/zookeeper/tree/master/src/recipes/lock)的目录有ZooKeeper的Lock实现。
要获得锁的客户端进行如下操作:
1.调用Create方法并使用"_locknode_/guid-lock-"作为路径,并设置sequence和ephemeral标志位。guid是防止create的结果缺失,详见下面说明。
2.在锁节点上调用getChildren方法,不用设置watch(这是为了防止羊群效应)。
3.如果第一步所创建的路径拥有最小的序号,该客户端获得锁并退出协议。
4.客户端调用exists并设置watch标志在比自身小的节点上。
5.如果exists返回false,返回第二步。不然等到上一步设置的watch事件触发再回到第二步。
解锁的步骤非常简单:客户端希望释放锁只需要简单地删除第一步所创建的锁。
这里有几点值得注意:
- 由于每个节点只会被一个客户端watch,所以删除一个节点只会唤醒一个客户端。这样可以避免羊群效应。
- 没有轮询或超时的问题
- 使用该方式实现锁可以方便地看见锁的竞争、中断锁占用、调试锁等
可恢复错误和GUID
- 如果调用create时发生了一个可恢复的错误,客户端可以调用getChildren并通过guid检查节点的名字,来得知自己创建的节点。这个解决了引言中提到的问题,即create成功返回但是服务器在获得节点名字之前宕机的情况。
共享锁
可以通过修改部分协议来实现共享锁。
获得读锁 | 获得写锁 |
---|---|
1.调用create创建"guid-/read-"节点。这是将会用到的读锁。需要确保使用sequence和ephemeral标志位。 2.在锁节点上调用getChildren,注意不要设置watch标志,避免羊群效应。 3.如果没有比第一步创建的节点小的"write-"开头的节点,则客户端获得锁并退出协议。 4.否则在次小的write节点上调用exists方法并设置watch标志。 5.如果exists方法返回false则返回第二步。 6.不然等待上面watch事件触发再返回第二步 | 1.调用create创建"guid-/write-"节点。这是后面需要的写锁。需要确保使用sequence和ephemeral标志位。 2.在锁节点上调用getChildren,注意不要设置watch标志,避免羊群效应。 3.如果没有比第一步创建的更小的节点,客户端获得锁并退出协议。 4.在次小的节点上调用exists方法并设置watch标志。 5.如果exists返回false回到第二步,不然等到watch事件触发再返回第二步。 |
注意:
- 该recipe可能产生羊群效应,即,当有一群客户端等待读锁时,客户端会在写锁释放时一起被唤醒。事实上,这个过程应该是正确的:所有等待的读客户端获得了锁。而羊群效应是指有一群节点被唤醒,而事实上只能允许少数可以运行。
- 详见上面提到的关于的guid使用。
可恢复的共享锁
对共享锁协议做一些小幅改动就可以实现锁的可恢复:
在上面读写锁的第一步,在调用create之后,立即调用getData并使用watch标识,如果客户端收到这个watch事件,再次调用getData并使用watch标识并查看内容是否包含"unlock"字符串,unlock表示客户端必须释放当前锁。根据共享锁协议,你可以向获得锁客户端调用setData写入"unlock"字符串,申请其释放锁。
注意这个协议需要锁持有者同意释放锁。这很重要,尤其是如果锁持有者需要在释放之前做某些操作时。当然你可以在你的协议中规定,在锁持有者一定时间后如仍未删除锁节点,可以由他人强制删除。
Curator实现
共享锁
public InterProcessSemaphoreMutex(CuratorFramework client,String path)
/**
client 客户端实例
path 锁节点地址
**/
public void acquire() //获得锁
public void release() //释放锁
可重入共享锁
public InterProcessMutex(CuratorFramework client,String path)
/**
client 客户端实例
path 锁节点地址
**/
public void acquire() //获得锁
public void release() //释放锁
public void makeRevocable(RevocationListener<T> listener) //撤销锁
public static void attemptRevoke(CuratorFramework client,String path) //请求对应路径锁撤销
可重入读写锁
public InterProcessReadWriteLock(CuratorFramework client,String basePath)
/**
client 客户端实例
basePath 锁基础路径
**/
public InterProcessLock readLock() //读锁
public InterProcessLock writeLock() //写锁