原文出处:
阿凡卢
在分布式系统中,往往需要一些分布式同步原语来做一些协同工作,上一篇文章介绍了Zookeeper的基本原理,本文介绍下基于Zookeeper的Lock和Queue的实现,主要代码都来自Zookeeper的官方recipe。
锁(Lock)
完全分布式锁是全局同步的,这意味着在任何时刻没有两个客户端会同时认为它们都拥有相同的锁,使用 Zookeeper 可以实现分布式锁,需要首先定义一个锁节点(lock root node)。
需要获得锁的客户端按照以下步骤来获取锁:
- 保证锁节点(lock root node)这个父根节点的存在,这个节点是每个要获取lock客户端共用的,这个节点是PERSISTENT的。
- 第一次需要创建本客户端要获取lock的节点,调用 create( ),并设置 节点为EPHEMERAL_SEQUENTIAL类型,表示该节点为临时的和顺序的。如果获取锁的节点挂掉,则该节点自动失效,可以让其他节点获取锁。
- 在父锁节点(lock root node)上调用 getChildren( ) ,不需要设置监视标志。 (为了避免“羊群效应”).
- 按照Fair竞争的原则,将步骤3中的子节点(要获取锁的节点)按照节点顺序的大小做排序,取出编号最小的一个节点做为lock的owner,判断自己的节点id
- 是否就为owner id,如果是则返回,lock成功。如果不是则调用 exists( )监听比自己小的前一位的id,关注它锁释放的操作(也就是exist watch)。
- 如果第4步监听exist的watch被触发,则继续按4中的原则判断自己是否能获取到lock。
释放锁:需要释放锁的客户端只需要删除在第2步中创建的节点即可。
注意事项:
一个节点的删除只会导致一个客户端被唤醒,因为每个节点只被一个客户端watch,这避免了“羊群效应”。
一个分布式lock的实现:
package org.apache.zookeeper.recipes.lock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import static org.apache.zookeeper.CreateMode.EPHEMERAL_SEQUENTIAL; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; /** * A <a href="package.html">protocol to implement an exclusive * write lock or to elect a leader</a>. <p/> You invoke {@link #lock()} to * start the process of grabbing the lock; you may get the lock then or it may be * some time later. <p/> You can register a listener so that you are invoked * when you get the lock; otherwise you can ask if you have the lock * by calling {@link #isOwner()} * */ public class WriteLock extends ProtocolSupport { private static final Logger LOG = LoggerFactory.getLogger(WriteLock.class); private final String dir; private String id; private ZNodeName idName; private String ownerId; private String lastChildId; private byte[] data = {0x12, 0x34}; private LockListener callback; private LockZooKeeperOperation zop; /** * zookeeper contructor for writelock * @param zookeeper zookeeper client instance * @param dir the parent path you want to use for locking * @param acls the acls that you want to use for all the paths, * if null world read/write is used. */ public WriteLock(ZooKeeper zookeeper, String dir, List<ACL> acl) { super(zookeeper); this.dir = dir; if (acl != null) { setAcl(acl); } this.zop = new LockZooKeeperOperation(); } /** * zookeeper contructor for writelock with callback * @param zookeeper the zookeeper client instance * @param dir the parent path you want to use for locking * @param acl the acls that you want to use for all the paths * @param callback the call back instance */ public WriteLock(ZooKeeper zookeeper, String dir, List<ACL> acl, LockListener callback) { this(zookeeper, dir, acl); this.callback = callback; } /** * return the current locklistener * @return the locklistener */ public LockListener getLockListener() { return this.callback; } /** * register a different call back listener * @param callback the call back instance */ public void setLockListener(Lock