ZooKeeper 五种经典应用场景详解
目录
- 分布式锁(Distributed Lock)
- 配置管理(Configuration Management)
- 服务注册与发现(Service Registration and Discovery)
- Leader选举(Leader Election)
- 分布式队列(Distributed Queue)
1. 分布式锁
简介
分布式锁是 ZooKeeper 最经典的应用场景之一,用于在分布式系统中实现互斥访问共享资源。通过 ZooKeeper 的临时顺序节点(Ephemeral Sequential Node)特性,可以实现公平的分布式锁机制。
架构图
示例代码
Java 实现
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.concurrent.TimeUnit;
public class DistributedLockExample {
private static final String ZK_ADDRESS = "localhost:2181";
private static final String LOCK_PATH = "/distributed-lock";
private CuratorFramework client;
private InterProcessMutex lock;
public DistributedLockExample() {
// 创建 Curator 客户端
client = CuratorFrameworkFactory.builder()
.connectString(ZK_ADDRESS)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
// 创建分布式锁
lock = new InterProcessMutex(client, LOCK_PATH);
}
/**
* 获取锁并执行业务逻辑
*/
public void doWorkWithLock() {
try {
// 尝试获取锁,最多等待10秒
if (lock.acquire(10, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread().getName() + " 获取到锁");
// 执行业务逻辑
doBusinessLogic();
} finally {
// 释放锁
lock.release();
System.out.println(Thread.currentThread().getName() + " 释放锁");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void doBusinessLogic() throws InterruptedException {
// 模拟业务处理
Thread.sleep(2000);
System.out.println("执行业务逻辑...");
}
public void close() {
if (client != null) {
client.close();
}
}
public static void main(String[] args) {
DistributedLockExample example = new DistributedLockExample();
// 模拟多个线程竞争锁
for (int i = 0; i < 5; i++) {
final int threadId = i;
new Thread(() -> {
example.doWorkWithLock();
}, "Thread-" + threadId).start();
}
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
example.close();
}
}
原生 ZooKeeper API 实现
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 NativeDistributedLock {
private static final String ZK_ADDRESS = "localhost:2181";
private static final String LOCK_ROOT = "/locks";
private static final String LOCK_PREFIX = "lock-";
private ZooKeeper zk;
private String lockPath;
private String currentLockPath;
public NativeDistributedLock() throws IOException, InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
zk = new ZooKeeper(ZK_ADDRESS, 3000, event -> {
if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
latch.countDown();
}
});
latch.await();
// 确保根节点存在
try {
if (zk.exists(LOCK_ROOT, false) == null) {
zk.create(LOCK_ROOT, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (KeeperException e) {
// 节点已存在,忽略
}
}
/**
* 获取锁
*/
public boolean tryLock() throws KeeperException, InterruptedException {
// 创建临时顺序节点
currentLockPath = zk.create(LOCK_ROOT + "/" + LOCK_PREFIX,
new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
// 获取所有锁节点并排序
List<String> children = zk.getChildren(LOCK_ROOT, false);
Collections.sort(children);
// 判断当前节点是否是最小的节点
String currentNode = currentLockPath.substring(LOCK_ROOT.length() + 1);
int index = children.indexOf(currentNode);
if (index == 0) {
// 当前节点是最小的,获取锁成功
return true;
} else {
// 监听前一个节点
String previousNode = LOCK_ROOT + "/" + children.get(index - 1);
final CountDownLatch latch = new CountDownLatch(1);
Stat stat = zk.exists(previousNode, event -> {
if (event.getType() == Watcher.Event.EventType.NodeDeleted) {
latch.countDown();
}
});
if (stat == null) {
// 前一个节点已不存在,重新尝试获取锁
return tryLock();
}
// 等待前一个节点被删除
latch.await();
return tryLock();
}
}
/**
* 释放锁
*/
public void unlock() throws KeeperException, InterruptedException {
if (currentLockPath != null) {
zk.delete(currentLockPath, -1);
currentLockPath = null;
}
}
public void close() throws InterruptedException {
if (zk != null) {
zk.close();
}
}
}
重难点分析
重点:
- 临时顺序节点:使用
EPHEMERAL_SEQUENTIAL模式创建节点,确保节点按顺序创建且会话断开时自动删除 - 公平锁机制:通过节点序号实现公平锁,序号最小的节点获得锁
- 监听机制:通过监听前一个节点的删除事件,实现锁的自动获取
难点:
- 惊群效应:多个客户端同时监听同一个节点,当节点删除时所有客户端都会被唤醒。解决方案是只监听前一个节点
- 会话超时:客户端会话超时会导致临时节点被删除,可能影响锁的正确性。需要实现会话重连机制
- 死锁检测:需要处理客户端异常退出导致的锁无法释放问题(临时节点会自动删除,但需要确保会话正常关闭)
2. 配置管理
简介
配置管理是 ZooKeeper 的另一个重要应用场景,用于集中管理分布式系统的配置信息。当配置发生变化时,所有订阅该配置的客户端都能实时收到通知并更新本地配置。
架构图
示例代码
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.nio.charset.StandardCharsets;
public class ConfigManager {
private static final String ZK_ADDRESS = "localhost:2181";
private static final String CONFIG_PATH = "/config/app-config";
private CuratorFramework client;
private NodeCache nodeCache;
private String currentConfig;
public ConfigManager() {
client = CuratorFrameworkFactory.builder()
.connectString(ZK_ADDRESS)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
// 创建节点缓存监听器
nodeCache = new NodeCache(client, CONFIG_PATH);
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
updateConfig();
}
});
try {
nodeCache.start(true);
updateConfig();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 更新配置
*/
private void updateConfig() {
try {
byte[] data = nodeCache.getCurrentData().getData();
currentConfig = new String(data, StandardCharsets.UTF_8);
System.out.println("配置已更新: " + currentConfig);
// 应用新配置
applyConfig(currentConfig);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 应用配置到业务逻辑
*/
private void applyConfig(String config) {
// 解析配置并应用到业务逻辑
System.out.println("应用配置: " + config);
// 例如:更新数据库连接池大小、线程池参数等
}
/**
* 获取当前配置
*/
public String getCurrentConfig() {
return currentConfig;
}
/**
* 更新配置(管理员操作)
*/
public void updateConfig(String newConfig) throws Exception {
client.setData().forPath(CONFIG_PATH, newConfig.getBytes(StandardCharsets.UTF_8));
}
/**
* 初始化配置
*/
public void initConfig(String initialConfig) throws Exception {
try {
client.create().creatingParentsIfNeeded()
.forPath(CONFIG_PATH, initialConfig.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
// 节点已存在,直接更新
updateConfig(initialConfig);
}
}
public void close() {
if (nodeCache != null) {
try {
nodeCache.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (client != null) {
client.close();
}
}
public static void main(String[] args) throws Exception {
ConfigManager configManager = new ConfigManager();
// 初始化配置
configManager.initConfig("{\"threadPoolSize\": 10, \"dbPoolSize\": 20}");
// 模拟配置更新
Thread.sleep(5000);
configManager.updateConfig("{\"threadPoolSize\": 20, \"dbPoolSize\": 30}");
Thread.sleep(10000);
configManager.close();
}
}
重难点分析
重点:
- 实时通知:使用
NodeCache或PathChildrenCache监听配置节点变化 - 配置版本管理:通过节点版本号实现配置的版本控制
- 配置回滚:保存历史配置,支持配置回滚
难点:
- 配置一致性:确保所有节点在同一时刻看到相同的配置。需要处理网络分区和节点故障的情况
- 配置冲突:多个管理员同时修改配置时的冲突处理。可以使用乐观锁(版本号)机制
- 配置格式:需要定义统一的配置格式(JSON、XML、Properties等),并处理格式错误的情况
3. 服务注册与发现
简介
服务注册与发现是微服务架构中的核心组件。服务提供者将自己的服务信息注册到 ZooKeeper,服务消费者从 ZooKeeper 获取可用的服务列表,实现服务的动态发现和负载均衡。
架构图
示例代码
服务注册
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class ServiceRegistry {
private static final String ZK_ADDRESS = "localhost:2181";
private static final String SERVICES_ROOT = "/services";
private CuratorFramework client;
private String serviceName;
private String servicePath;
private String currentProviderPath;
public ServiceRegistry(String serviceName) {
this.serviceName = serviceName;
this.servicePath = SERVICES_ROOT + "/" + serviceName;
client = CuratorFrameworkFactory.builder()
.connectString(ZK_ADDRESS)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
}
/**
* 注册服务
*/
public void register(String host, int port) throws Exception {
// 确保服务根节点存在
try {
if (client.checkExists().forPath(SERVICES_ROOT) == null) {
client.create().creatingParentsIfNeeded()
.forPath(SERVICES_ROOT);
}
} catch (Exception e) {
// 节点已存在
}
// 创建服务提供者节点(临时节点)
String providerInfo = host + ":" + port;
currentProviderPath = client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
.forPath(servicePath + "/provider-",
providerInfo.getBytes(StandardCharsets.UTF_8));
System.out.println("服务注册成功: " + currentProviderPath + " -> " + providerInfo);
}
/**
* 注销服务
*/
public void unregister() {
if (currentProviderPath != null) {
try {
client.delete().forPath(currentProviderPath);
System.out.println("服务注销成功: " + currentProviderPath);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void close() {
unregister();
if (client != null) {
client.close();
}
}
}
服务发现
public class ServiceDiscovery {
private static final String ZK_ADDRESS = "localhost:2181";
private static final String SERVICES_ROOT = "/services";
private CuratorFramework client;
private String serviceName;
private String servicePath;
private PathChildrenCache pathChildrenCache;
private List<String> serviceProviders;
public ServiceDiscovery(String serviceName) {
this.serviceName = serviceName;
this.servicePath = SERVICES_ROOT + "/" + serviceName;
this.serviceProviders = new ArrayList<>();
client = CuratorFrameworkFactory.builder()
.connectString(ZK_ADDRESS)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
// 监听服务提供者列表变化
pathChildrenCache = new PathChildrenCache(client, servicePath, true);
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
throws Exception {
updateServiceProviders();
}
});
try {
pathChildrenCache.start();
updateServiceProviders();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 更新服务提供者列表
*/
private void updateServiceProviders() {
try {
List<String> providers = new ArrayList<>();
List<String> children = client.getChildren().forPath(servicePath);
for (String child : children) {
String childPath = servicePath + "/" + child;
byte[] data = client.getData().forPath(childPath);
String providerInfo = new String(data, StandardCharsets.UTF_8);
providers.add(providerInfo);
}
synchronized (this) {
serviceProviders = providers;
}
System.out.println("服务列表已更新: " + serviceProviders);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取服务提供者列表
*/
public synchronized List<String> getServiceProviders() {
return new ArrayList<>(serviceProviders);
}
/**
* 负载均衡:随机选择一个服务提供者
*/
public String getRandomProvider() {
List<String> providers = getServiceProviders();
if (providers.isEmpty()) {
return null;
}
int index = (int) (Math.random() * providers.size());
return providers.get(index);
}
/**
* 负载均衡:轮询选择服务提供者
*/
private int roundRobinIndex = 0;
public String getRoundRobinProvider() {
List<String> providers = getServiceProviders();
if (providers.isEmpty()) {
return null;
}
String provider = providers.get(roundRobinIndex % providers.size());
roundRobinIndex++;
return provider;
}
public void close() {
if (pathChildrenCache != null) {
try {
pathChildrenCache.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (client != null) {
client.close();
}
}
}
重难点分析
重点:
- 临时节点:使用临时节点存储服务提供者信息,服务下线时自动删除
- 动态监听:使用
PathChildrenCache监听服务列表变化,实现服务的动态发现 - 负载均衡:实现多种负载均衡策略(随机、轮询、一致性哈希等)
难点:
- 服务健康检查:ZooKeeper 只能检测到会话断开,无法检测服务是否真正可用。需要结合心跳机制或健康检查接口
- 服务雪崩:当大量服务同时注册或注销时,可能触发大量事件。需要实现事件合并和防抖机制
- 网络分区:网络分区可能导致服务注册信息不一致。需要实现分区恢复后的数据同步机制
4. Leader选举
简介
Leader 选举是分布式系统中的经典问题,用于在多个节点中选出一个主节点来协调工作。ZooKeeper 通过临时顺序节点和最小序号机制,可以优雅地实现 Leader 选举。
架构图
示例代码
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.concurrent.TimeUnit;
public class LeaderElectionExample {
private static final String ZK_ADDRESS = "localhost:2181";
private static final String ELECTION_PATH = "/election";
private CuratorFramework client;
private LeaderLatch leaderLatch;
private String nodeId;
public LeaderElectionExample(String nodeId) {
this.nodeId = nodeId;
client = CuratorFrameworkFactory.builder()
.connectString(ZK_ADDRESS)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
leaderLatch = new LeaderLatch(client, ELECTION_PATH, nodeId);
leaderLatch.addListener(new LeaderLatchListener() {
@Override
public void isLeader() {
System.out.println("节点 " + nodeId + " 成为 Leader");
startLeaderWork();
}
@Override
public void notLeader() {
System.out.println("节点 " + nodeId + " 不再是 Leader");
stopLeaderWork();
}
});
}
/**
* 参与选举
*/
public void start() throws Exception {
leaderLatch.start();
}
/**
* 检查是否是 Leader
*/
public boolean isLeader() {
return leaderLatch.hasLeadership();
}
/**
* 等待成为 Leader(带超时)
*/
public boolean awaitLeadership(long timeout, TimeUnit unit) throws InterruptedException {
return leaderLatch.await(timeout, unit);
}
/**
* Leader 的工作
*/
private void startLeaderWork() {
System.out.println("节点 " + nodeId + " 开始执行 Leader 工作");
// 例如:定时任务调度、数据同步等
}
/**
* 停止 Leader 工作
*/
private void stopLeaderWork() {
System.out.println("节点 " + nodeId + " 停止 Leader 工作");
}
/**
* 退出选举
*/
public void close() throws Exception {
if (leaderLatch != null) {
leaderLatch.close();
}
if (client != null) {
client.close();
}
}
public static void main(String[] args) throws Exception {
// 创建多个节点参与选举
LeaderElectionExample node1 = new LeaderElectionExample("node-1");
LeaderElectionExample node2 = new LeaderElectionExample("node-2");
LeaderElectionExample node3 = new LeaderElectionExample("node-3");
node1.start();
node2.start();
node3.start();
// 等待选举完成
Thread.sleep(2000);
// 检查 Leader
if (node1.isLeader()) {
System.out.println("Node-1 是 Leader");
}
if (node2.isLeader()) {
System.out.println("Node-2 是 Leader");
}
if (node3.isLeader()) {
System.out.println("Node-3 是 Leader");
}
// 模拟 Leader 节点退出
Thread.sleep(5000);
if (node1.isLeader()) {
node1.close();
System.out.println("Leader 节点退出,重新选举...");
}
Thread.sleep(2000);
// 清理
node1.close();
node2.close();
node3.close();
}
}
使用原生 API 实现 Leader 选举
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 NativeLeaderElection implements Watcher {
private static final String ZK_ADDRESS = "localhost:2181";
private static final String ELECTION_ROOT = "/election";
private static final String NODE_PREFIX = "node-";
private ZooKeeper zk;
private String currentNodePath;
private String watchedNodePath;
private boolean isLeader = false;
private LeaderElectionListener listener;
public interface LeaderElectionListener {
void onElected();
void onRevoked();
}
public NativeLeaderElection(LeaderElectionListener listener)
throws IOException, InterruptedException {
this.listener = listener;
CountDownLatch latch = new CountDownLatch(1);
zk = new ZooKeeper(ZK_ADDRESS, 3000, event -> {
if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
latch.countDown();
}
});
latch.await();
// 确保根节点存在
try {
if (zk.exists(ELECTION_ROOT, false) == null) {
zk.create(ELECTION_ROOT, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (KeeperException e) {
// 节点已存在
}
}
/**
* 参与选举
*/
public void participate() throws KeeperException, InterruptedException {
// 创建临时顺序节点
currentNodePath = zk.create(ELECTION_ROOT + "/" + NODE_PREFIX,
new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
checkAndBecomeLeader();
}
/**
* 检查并成为 Leader
*/
private void checkAndBecomeLeader() throws KeeperException, InterruptedException {
List<String> children = zk.getChildren(ELECTION_ROOT, false);
Collections.sort(children);
String currentNode = currentNodePath.substring(ELECTION_ROOT.length() + 1);
int index = children.indexOf(currentNode);
if (index == 0) {
// 当前节点是最小的,成为 Leader
if (!isLeader) {
isLeader = true;
System.out.println("成为 Leader: " + currentNodePath);
if (listener != null) {
listener.onElected();
}
}
} else {
// 不是 Leader,监听前一个节点
if (isLeader) {
isLeader = false;
System.out.println("不再是 Leader: " + currentNodePath);
if (listener != null) {
listener.onRevoked();
}
}
String previousNode = ELECTION_ROOT + "/" + children.get(index - 1);
watchNode(previousNode);
}
}
/**
* 监听节点
*/
private void watchNode(String nodePath) throws KeeperException, InterruptedException {
watchedNodePath = nodePath;
Stat stat = zk.exists(nodePath, this);
if (stat == null) {
// 前一个节点已不存在,重新检查
checkAndBecomeLeader();
}
}
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeDeleted) {
if (event.getPath().equals(watchedNodePath)) {
try {
checkAndBecomeLeader();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* 检查是否是 Leader
*/
public boolean isLeader() {
return isLeader;
}
public void close() throws InterruptedException {
if (zk != null) {
zk.close();
}
}
}
重难点分析
重点:
- 最小序号机制:序号最小的节点成为 Leader,简单高效
- 自动故障转移:Leader 节点故障时,下一个节点自动成为新的 Leader
- 临时节点:使用临时节点确保节点故障时自动从选举中移除
难点:
- 脑裂问题:网络分区可能导致多个 Leader 同时存在。需要结合其他机制(如 fencing token)来防止脑裂
- 选举风暴:大量节点同时参与选举时可能产生大量事件。需要优化监听机制
- Leader 切换开销:Leader 切换时可能需要重新加载状态、重建连接等,需要最小化切换开销
5. 分布式队列
简介
分布式队列用于在分布式系统中实现任务队列,支持多生产者多消费者模式。ZooKeeper 可以通过顺序节点和监听机制实现分布式队列。
架构图
示例代码
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.queue.DistributedQueue;
import org.apache.curator.framework.recipes.queue.QueueBuilder;
import org.apache.curator.framework.recipes.queue.QueueConsumer;
import org.apache.curator.framework.recipes.queue.QueueSerializer;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.nio.charset.StandardCharsets;
public class DistributedQueueExample {
private static final String ZK_ADDRESS = "localhost:2181";
private static final String QUEUE_PATH = "/distributed-queue";
/**
* 队列序列化器
*/
public static class StringQueueSerializer implements QueueSerializer<String> {
@Override
public byte[] serialize(String item) {
return item.getBytes(StandardCharsets.UTF_8);
}
@Override
public String deserialize(byte[] bytes) {
return new String(bytes, StandardCharsets.UTF_8);
}
}
/**
* 队列消费者
*/
public static class StringQueueConsumer implements QueueConsumer<String> {
private String consumerName;
public StringQueueConsumer(String consumerName) {
this.consumerName = consumerName;
}
@Override
public void consumeMessage(String message) throws Exception {
System.out.println(consumerName + " 消费消息: " + message);
// 处理消息
processMessage(message);
}
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
System.out.println(consumerName + " 连接状态变化: " + newState);
}
private void processMessage(String message) {
// 模拟消息处理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
/**
* 生产者
*/
public static class QueueProducer {
private CuratorFramework client;
private DistributedQueue<String> queue;
public QueueProducer() {
client = CuratorFrameworkFactory.builder()
.connectString(ZK_ADDRESS)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
queue = QueueBuilder.builder(client,
new StringQueueConsumer("Producer"),
new StringQueueSerializer(),
QUEUE_PATH)
.buildQueue();
try {
queue.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void put(String message) throws Exception {
queue.put(message);
System.out.println("生产消息: " + message);
}
public void close() throws Exception {
if (queue != null) {
queue.close();
}
if (client != null) {
client.close();
}
}
}
/**
* 消费者
*/
public static class QueueConsumer {
private CuratorFramework client;
private DistributedQueue<String> queue;
private StringQueueConsumer consumer;
public QueueConsumer(String consumerName) {
consumer = new StringQueueConsumer(consumerName);
client = CuratorFrameworkFactory.builder()
.connectString(ZK_ADDRESS)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
queue = QueueBuilder.builder(client,
consumer,
new StringQueueSerializer(),
QUEUE_PATH)
.buildQueue();
try {
queue.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void close() throws Exception {
if (queue != null) {
queue.close();
}
if (client != null) {
client.close();
}
}
}
public static void main(String[] args) throws Exception {
// 创建生产者
QueueProducer producer = new QueueProducer();
// 创建多个消费者
QueueConsumer consumer1 = new QueueConsumer("Consumer-1");
QueueConsumer consumer2 = new QueueConsumer("Consumer-2");
QueueConsumer consumer3 = new QueueConsumer("Consumer-3");
// 生产消息
for (int i = 0; i < 10; i++) {
producer.put("Message-" + i);
Thread.sleep(500);
}
// 等待消费完成
Thread.sleep(20000);
// 清理
producer.close();
consumer1.close();
consumer2.close();
consumer3.close();
}
}
使用原生 API 实现简单队列
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 SimpleDistributedQueue {
private static final String ZK_ADDRESS = "localhost:2181";
private static final String QUEUE_ROOT = "/queue";
private static final String ITEM_PREFIX = "item-";
private ZooKeeper zk;
public SimpleDistributedQueue() throws IOException, InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
zk = new ZooKeeper(ZK_ADDRESS, 3000, event -> {
if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
latch.countDown();
}
});
latch.await();
// 确保根节点存在
try {
if (zk.exists(QUEUE_ROOT, false) == null) {
zk.create(QUEUE_ROOT, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (KeeperException e) {
// 节点已存在
}
}
/**
* 入队
*/
public void offer(byte[] data) throws KeeperException, InterruptedException {
zk.create(QUEUE_ROOT + "/" + ITEM_PREFIX,
data,
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT_SEQUENTIAL);
}
/**
* 出队(阻塞)
*/
public byte[] take() throws KeeperException, InterruptedException {
while (true) {
List<String> children = zk.getChildren(QUEUE_ROOT, false);
if (children.isEmpty()) {
// 队列为空,等待
final CountDownLatch latch = new CountDownLatch(1);
zk.getChildren(QUEUE_ROOT, event -> {
if (event.getType() == Watcher.Event.EventType.NodeChildrenChanged) {
latch.countDown();
}
});
latch.await();
continue;
}
// 排序并获取最小的节点
Collections.sort(children);
String firstChild = children.get(0);
String firstChildPath = QUEUE_ROOT + "/" + firstChild;
try {
byte[] data = zk.getData(firstChildPath, false, null);
zk.delete(firstChildPath, -1);
return data;
} catch (KeeperException.NoNodeException e) {
// 节点已被其他消费者删除,重试
continue;
}
}
}
/**
* 出队(非阻塞)
*/
public byte[] poll() throws KeeperException, InterruptedException {
List<String> children = zk.getChildren(QUEUE_ROOT, false);
if (children.isEmpty()) {
return null;
}
Collections.sort(children);
String firstChild = children.get(0);
String firstChildPath = QUEUE_ROOT + "/" + firstChild;
try {
byte[] data = zk.getData(firstChildPath, false, null);
zk.delete(firstChildPath, -1);
return data;
} catch (KeeperException.NoNodeException e) {
return null;
}
}
/**
* 获取队列大小
*/
public int size() throws KeeperException, InterruptedException {
return zk.getChildren(QUEUE_ROOT, false).size();
}
public void close() throws InterruptedException {
if (zk != null) {
zk.close();
}
}
}
重难点分析
重点:
- FIFO 保证:通过顺序节点和排序机制保证先进先出
- 并发安全:多个消费者同时消费时的并发控制
- 阻塞消费:队列为空时消费者阻塞等待
难点:
- 重复消费:多个消费者可能同时获取到同一个消息。需要使用原子操作(如
getData+delete的原子性)或分布式锁 - 消息丢失:消费者获取消息后但在处理前崩溃,可能导致消息丢失。可以使用临时节点或事务机制
- 性能优化:大量消息时的性能问题。可以考虑批量操作、异步处理等优化手段
总结
ZooKeeper 作为分布式协调服务,在上述五种场景中都发挥了重要作用:
- 分布式锁:解决分布式环境下的互斥访问问题
- 配置管理:实现配置的集中管理和动态更新
- 服务注册与发现:支持微服务架构中的服务治理
- Leader 选举:实现分布式系统中的主从切换
- 分布式队列:提供分布式环境下的任务队列功能
通用最佳实践
- 使用 Curator 框架:Curator 提供了更高级的抽象,简化了 ZooKeeper 的使用
- 处理连接断开:实现重连机制和状态恢复
- 监控和日志:记录关键操作,便于问题排查
- 性能优化:合理使用缓存、批量操作等优化手段
- 错误处理:妥善处理各种异常情况,确保系统稳定性
注意事项
- ZooKeeper 不适合存储大量数据,主要用于协调和元数据管理
- 注意会话超时设置,避免因网络抖动导致节点被误删
- 合理设置监听器,避免监听风暴
- 考虑 ZooKeeper 的性能限制,对于高并发场景需要结合其他技术
1330

被折叠的 条评论
为什么被折叠?



