使用zookeeper实现一个分布式锁

77 阅读1分钟

可以继承 aqs 重写 tryAcquiretryRelease 方法,在zookeeper里成功写入临时顺序节点就调用 compareAndSetState 方法修改state,业务处理结束删除临时顺序节点。不得不说 aqs 真的很方便。

所以换成信号量 CountDownLatch 来实现一个。

为了提高性能,应该避免羊群效应,改用监听前一个节点的删除事件。

public class ZkLock extends Lock {

private static final String LOCK_PATH = "/lock";
//当前节点路径
private String currentPath;
//前一个节点的路径
private String beforePath;
    //zookeeper客户端
    private ZKClient client;

private CountDownLatch countDownLatch = null;

public ZkLock(ZKClient client) {
            this.client = client;
	//如果不存在这个节点,则创建持久节点
	if (!zkClient.exists(PATH)) {		
		zkClient.createPersistent(PATH);
	}
}
    
    @Override
    public void lock() {
        if (tryLock()) {
            System.out.println("加锁成功");
        } else {
            System.out.println("等待锁释放");
            waitLock();
        }
    }

@Override
public void releaseLock() {
	if (null != client) {
                client.delete(currentPath);
                client.close();
	}

}

@Override
public boolean tryLock() {
	//如果currentPath为空则为第一次尝试加锁,第一次加锁赋值currentPath
	if (null == currentPath || "".equals(currentPath)) {
		//在path下创建一个临时的顺序节点
		currentPath = client.createEphemeralSequential(PATH+"/", "lock");
	}
	//获取所有的临时节点,并排序
	List<String> childrens = client.getChildren(PATH);
	Collections.sort(childrens);
	if (currentPath.equals(PATH+"/"+childrens.get(0))) {
		return true;
	}else {//如果当前节点不是排名第一,则获取它前面的节点名称,并赋值给beforePath
		int pathLength = PATH.length();
		int wz = Collections.binarySearch(childrens, currentPath.substring(pathLength+1));
		beforePath = PATH+"/"+childrens.get(wz-1);
	}
	return false;
}

@Override
public void waitLock() {
	IZkDataListener listener = new IZkDataListener() {
		
		@Override
		public void handleDataDeleted(String dataPath) throws Exception {
			if (null != countDownLatch){
				countDownLatch.countDown();
			}
		}
		
		@Override
		public void handleDataChange(String dataPath, Object data) throws Exception {
			
		}
	};
	//监听前一个节点的变化
	zkClient.subscribeDataChanges(beforePath, listener);
	if (client.exists(beforePath)) {
		countDownLatch = new CountDownLatch(1);
		try {
                            // 阻塞等待前一个节点被删除,即没有锁
			countDownLatch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	zkClient.unsubscribeDataChanges(beforePath, lIZkDataListener);
}

}