分布式协调是分布式系统中一个关键的需求,用于协调多个节点之间的工作,确保数据一致性和任务同步。ZooKeeper 提供了一套原语(如分布式锁、队列、屏障等),使得实现分布式协调变得更加简单和高效。
实现原理
- 分布式锁:利用 ZooKeeper 的临时节点或临时顺序节点,可以实现分布式锁,确保只有一个节点能够获取锁。
- 分布式队列:利用 ZooKeeper 的节点存储和顺序节点,可以实现分布式队列,确保任务按顺序执行。
- 分布式屏障:利用 ZooKeeper 的节点存储和监听机制,可以实现分布式屏障,确保所有节点都到达某个点后再继续执行。
代码示例
以下是一些实现分布式协调的代码示例,展示了如何在 ZooKeeper 中实现分布式锁、分布式队列和分布式屏障。
依赖导入
首先,确保你已经导入了 ZooKeeper 的 Java 客户端库:
xml
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.7.0</version>
</dependency>
分布式锁实现
java
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class DistributedLock implements Watcher {
private ZooKeeper zooKeeper;
private String lockPath;
private String currentLockNode;
private CountDownLatch connectedSignal = new CountDownLatch(1);
private CountDownLatch lockAcquiredSignal = new CountDownLatch(1);
public DistributedLock(String connectString, String lockPath) throws IOException, InterruptedException {
this.zooKeeper = new ZooKeeper(connectString, 3000, this);
this.lockPath = lockPath;
connectedSignal.await();
ensureLockPath();
}
private void ensureLockPath() {
try {
Stat stat = zooKeeper.exists(lockPath, false);
if (stat == null) {
zooKeeper.create(lockPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
public void acquireLock() {
try {
currentLockNode = zooKeeper.create(lockPath + "/lock_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
attemptLock();
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
private void attemptLock() throws KeeperException, InterruptedException {
List<String> children = zooKeeper.getChildren(lockPath, false);
Collections.sort(children);
String smallestNode = children.get(0);
if (currentLockNode.endsWith(smallestNode)) {
lockAcquiredSignal.countDown();
System.out.println("Lock acquired: " + currentLockNode);
} else {
String watchNode = null;
for (String child : children) {
if (child.compareTo(currentLockNode.substring(currentLockNode.lastIndexOf('/') + 1)) < 0) {
watchNode = child;
} else {
break;
}
}
if (watchNode != null) {
final String watchNodePath = lockPath + "/" + watchNode;
Stat stat = zooKeeper.exists(watchNodePath, this);
if (stat == null) {
attemptLock();
}
}
}
}
public void releaseLock() {
try {
zooKeeper.delete(currentLockNode, -1);
System.out.println("Lock released: " + currentLockNode);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.SyncConnected) {
connectedSignal.countDown();
} else if (event.getType() == Event.EventType.NodeDeleted) {
try {
attemptLock();
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
DistributedLock lock = new DistributedLock("localhost:2181", "/locks");
lock.acquireLock();
lock.lockAcquiredSignal.await();
// Perform critical section operations here
lock.releaseLock();
}
}
详细说明
-
初始化 ZooKeeper 客户端:
javapublic DistributedLock(String connectString, String lockPath) throws IOException, InterruptedException { this.zooKeeper = new ZooKeeper(connectString, 3000, this); this.lockPath = lockPath; connectedSignal.await(); ensureLockPath(); }
在初始化时,连接到 ZooKeeper 服务器,并确保锁路径存在。如果节点不存在,则创建一个持久节点表示锁路径。
-
确保锁路径存在:
javaprivate void ensureLockPath() { try { Stat stat = zooKeeper.exists(lockPath, false); if (stat == null) { zooKeeper.create(lockPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } catch (KeeperException | InterruptedException e) { e.printStackTrace(); } }
检查锁路径是否存在,如果不存在,则创建一个持久节点。
-
获取锁:
javapublic void acquireLock() { try { currentLockNode = zooKeeper.create(lockPath + "/lock_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); attemptLock(); } catch (KeeperException | InterruptedException e) { e.printStackTrace(); } }
每个获取锁的请求在锁路径下创建一个临时顺序节点,并尝试获取锁。
-
尝试获取锁:
javaprivate void attemptLock() throws KeeperException, InterruptedException { List<String> children = zooKeeper.getChildren(lockPath, false); Collections.sort(children); String smallestNode = children.get(0); if (currentLockNode.endsWith(smallestNode)) { lockAcquiredSignal.countDown(); System.out.println("Lock acquired: " + currentLockNode); } else { String watchNode = null; for (String child : children) { if (child.compareTo(currentLockNode.substring(currentLockNode.lastIndexOf('/') + 1)) < 0) { watchNode = child; } else { break; } } if (watchNode != null) { final String watchNodePath = lockPath + "/" + watchNode; Stat stat = zooKeeper.exists(watchNodePath, this); if (stat == null) { attemptLock(); } } } }
获取锁路径下的所有子节点,并根据节点名称排序。如果当前节点是最小的节点,则表示获取锁成功;否则,监听前一个节点的删除事件。
-
释放锁:
javapublic void releaseLock() { try { zooKeeper.delete(currentLockNode, -1); System.out.println("Lock released: " + currentLockNode); } catch (KeeperException | InterruptedException e) { e.printStackTrace(); } }
释放锁时,删除当前节点。
-
事件处理:
java@Override public void process(WatchedEvent event) { if (event.getState() == Event.KeeperState.SyncConnected) { connectedSignal.countDown(); } else if (event.getType() == Event.EventType.NodeDeleted) { try { attemptLock(); } catch (KeeperException | InterruptedException e) { e.printStackTrace(); } } }
当 ZooKeeper 客户端连接建立时,释放连接信号。当前一个节点被删除时,重新尝试获取锁。
-
主函数:
javapublic static void main(String[] args) throws Exception { DistributedLock lock = new DistributedLock("localhost:2181", "/locks"); lock.acquireLock(); lock.lockAcquiredSignal.await(); // Perform critical section operations here lock.releaseLock(); }
主函数创建一个分布式锁,并尝试获取锁,执行临界区操作后释放锁。
性能优化建议
-
异步操作:
- 使用 ZooKeeper 的异步 API,减少同步阻塞,提高并发性能。
-
批处理操作:
- 可以通过一次性读取多个节点的状态来减少网络请求的次数,提高性能。
-
本地缓存:
- 在客户端实现本地缓存,减少频繁的读请求,提升系统性能。
通过合理的设计和实现,ZooKeeper 可以有效地解决分布式协调的需求,确保系统的高可用性和一致性。