Zookeeper 分布式锁

135 阅读2分钟
  • 分布式锁有多种实现方式,比如通过数据库、redis都可实现。作为分布式协同 工具ZooKeeper,当然也有着标准的实现方式。
  • 设计思路
  1. 每个客户端往/Locks下创建临时有序节点/Locks/Lock_00000x
  2. 客户端取得/Locks下子节点,并进行排序,判断排在最前面的是否为自己,如果自己的锁节点在第一位,代表获取锁成功
  3. 如果自己的锁节点不在第一位,则监听自己前一位的锁节点。例如,自己锁节点 Lock 000000001,则监听前一个锁节点Lock 000000000
  4. 监听客户端重新执行第2步逻辑,判断自己是否获得了锁
  5. 获取到锁之后,就可以执行自己的操作。执行完之后,删除自己的锁节点。

1. 实验代码

public class DistributedLock {
  private String ip = null;  // ip及端口
  private static Integer timeOut = 5000;  // 超时时间,毫秒为单位
  private final static CountDownLatch countDownLatch = new CountDownLatch(1);
  private final static Logger log = Logger.getLogger(DistributedLock.class);
  private ZooKeeper zooKeeper = null;
  private static final String LOCK_ROOT_PATH = "/Locks";
  private static final String LOCK_NODE_NAME = "Lock_";
  String lockPath; // 锁节点的路径

  public DistributedLock() {
  }

  public DistributedLock(String ip) {
    this(ip, timeOut);
  }

  public DistributedLock(String ip, Integer timeOut) {
    this.ip = ip;
    DistributedLock.timeOut = timeOut;
    try {
      initZK(ip, timeOut);
    } catch (IOException | InterruptedException e) {
      e.printStackTrace();
    }
  }

  private void initZK(String ip, Integer timeOut) throws IOException, InterruptedException {
    zooKeeper = new ZooKeeper(ip, timeOut, new Watcher() {
      @Override
      public void process(WatchedEvent watchedEvent) {
        if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
          log.info("连接成功");
          countDownLatch.countDown();
        }
      }
    });
    countDownLatch.await();
  }

  public void release() {
    try {
      zooKeeper.delete(lockPath, -1);  // 删除本节点 
      zooKeeper.close();  // 关闭客户端
      log.info("已释放" + this.lockPath);
    } catch (InterruptedException | KeeperException e) {
      e.printStackTrace();
    }
  }

  /**
   * 需要锁,如果没有到自己,那就阻塞等
   */
  public void acquire() {
    try {
      createLock();  // 创建锁节点
      attemptLock();  // 祈求获取节点,没有的话就阻塞
    } catch (KeeperException | InterruptedException e) {
      e.printStackTrace();
    }
  }

  /**
   * 创建一个自己的号码
   */
  private void createLock() throws KeeperException, InterruptedException {
    if (zooKeeper.exists(LOCK_ROOT_PATH, true) == null) {
      zooKeeper.create(LOCK_ROOT_PATH, "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
      log.info("创建父节点: " + LOCK_ROOT_PATH + " 成功");
    }
    lockPath = zooKeeper.create(LOCK_ROOT_PATH + "/" + LOCK_NODE_NAME, "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    log.info("创建节点成功:" + lockPath);
  }

  /**
   * 一个监视器,监视上一个节点的删除
   */
  Watcher watcher = new Watcher() {
    @Override
    public void process(WatchedEvent watchedEvent) {
      if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
        synchronized (this) {
          notifyAll();
        }
      }
    }
  };

  /**
   * 获取锁。如果还没轮到自己,那就wait(),直到前一个节点的删除,唤醒自己
   */
  private void attemptLock() throws KeeperException, InterruptedException {
    List<String> children = zooKeeper.getChildren(LOCK_ROOT_PATH, null);
    Collections.sort(children);
    int index = children.indexOf(lockPath.substring(LOCK_ROOT_PATH.length() + 1));
    if (index == 0) {
      log.info("获取锁成功");
      return;
    }
    String lastPath = children.get(index - 1);  // 获取上一个节点的path
    // 添加一个监视前一个节点的监视器
    Stat stat = zooKeeper.exists(LOCK_ROOT_PATH + "/" + lastPath, watcher);
    if (stat == null) {  // 如果目前是空的,说明已经被删除了
      attemptLock();
      return;
    }
    synchronized (watcher) {
      watcher.wait();
    }
    attemptLock();
  }
}
public class DistributedLockTest {
  public static void main(String[] args) {
    DistributedLock lock = new DistributedLock("192.168.233.133:2181");
    lock.acquire();
    try {
      Thread.sleep(20000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    lock.release();
  }
}