Zookeeper入门教程 - 分布式协调服务从零开始学习
Zookeeper入门教程 - 分布式协调服务从零开始学习
目录
- Zookeeper简介
- 环境搭建
- Zookeeper核心概念
- Zookeeper安装与配置
- Zookeeper命令行操作
- Java客户端开发
- Watcher监听机制
- Zookeeper应用场景
- Zookeeper集群部署
- Zookeeper最佳实践
- 常见问题与解决方案
- 总结与进阶
1. Zookeeper简介
Apache Zookeeper是一个开源的分布式协调服务,由雅虎公司开发,是Google Chubby的开源实现。Zookeeper为分布式应用提供一致性服务,包括配置维护、域名服务、分布式同步、组服务等。
核心特性:
- ✅ 高可用性:集群模式保证服务高可用
- ✅ 顺序一致性:客户端更新请求按顺序执行
- ✅ 原子性:更新操作要么全部成功,要么全部失败
- ✅ 单一视图:无论客户端连接到哪个服务器,看到的数据都是一致的
- ✅ 可靠性:一旦更新成功,数据会持久化保存
- ✅ 实时性:保证客户端在特定时间范围内能够读取到最新数据
主要应用场景:
- 配置管理:集中管理分布式系统的配置信息
- 命名服务:提供统一的命名空间
- 分布式锁:实现分布式环境下的互斥访问
- 集群管理:监控集群节点状态
- Master选举:选举集群中的主节点
- 分布式队列:实现分布式环境下的队列
- 注册中心:作为服务注册与发现的中心(如Dubbo、Kafka)
Zookeeper的数据模型类似于文件系统的树形结构,每个节点称为ZNode(Zookeeper Node)。与文件系统不同的是:
ZNode可以存储数据(最大1MB)
ZNode可以有关联的子节点
ZNode有版本号,支持版本控制
ZNode有类型:持久节点、临时节点、顺序节点
当前稳定版本:3.9.x
推荐版本:3.7.x 或 3.8.x(生产环境)
最新版本:3.9.x(新特性)
2. 环境搭建
操作系统:
- Linux(推荐)
- macOS
- Windows(不推荐生产环境)
Java环境:
- JDK 8或更高版本(推荐JDK 11或JDK 17)
内存:
- 至少512MB可用内存
- 推荐2GB以上
磁盘:
- 至少1GB可用空间
- 推荐SSD存储
方式1:官网下载
- 访问Apache Zookeeper官网:https://zookeeper.apache.org/
- 进入下载页面:https://zookeeper.apache.org/releases.html
- 下载最新稳定版本(如:apache-zookeeper-3.9.0-bin.tar.gz)
方式2:使用wget下载
# 下载Zookeeper
wget https://archive.apache.org/dist/zookeeper/zookeeper-3.9.0/apache-zookeeper-3.9.0-bin.tar.gz
# 或者使用curl
curl -O https://archive.apache.org/dist/zookeeper/zookeeper-3.9.0/apache-zookeeper-3.9.0-bin.tar.gzLinux/macOS安装步骤
# 1. 解压安装包
tar -xzf apache-zookeeper-3.9.0-bin.tar.gz
# 2. 移动到目标目录(可选)
sudo mv apache-zookeeper-3.9.0 /opt/zookeeper
# 3. 创建数据目录和日志目录
mkdir -p /opt/zookeeper/data
mkdir -p /opt/zookeeper/logs
# 4. 设置环境变量(可选)
export ZOOKEEPER_HOME=/opt/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/binWindows安装步骤
- 解压下载的zip文件到目标目录(如:
C:\zookeeper) - 创建数据目录:
C:\zookeeper\data - 创建日志目录:
C:\zookeeper\logs - 配置环境变量(可选)
2.4 验证安装
# 检查Zookeeper版本
cd /opt/zookeeper
bin/zkServer.sh version
# 应该显示类似:
# ZooKeeper JMX enabled by default
# Using config: /opt/zookeeper/bin/../conf/zoo.cfg
# ZooKeeper 3.9.03. Zookeeper核心概念
Zookeeper中的节点(ZNode)有四种类型:
持久节点(PERSISTENT)
- 节点创建后一直存在,直到被显式删除
- 即使客户端断开连接,节点仍然存在
# 创建持久节点
create /persistent_node "data"临时节点(EPHEMERAL)
- 节点的生命周期与客户端会话绑定
- 客户端断开连接后,节点自动删除
- 不能有子节点
# 创建临时节点
create -e /ephemeral_node "data"持久顺序节点(PERSISTENT_SEQUENTIAL)
- 持久节点 + 顺序特性
- 节点名后会自动追加顺序号
# 创建持久顺序节点
create -s /persistent_seq "data"
# 结果:/persistent_seq0000000001临时顺序节点(EPHEMERAL_SEQUENTIAL)
- 临时节点 + 顺序特性
- 常用于分布式锁实现
# 创建临时顺序节点
create -s -e /ephemeral_seq "data"
# 结果:/ephemeral_seq0000000001每个ZNode包含以下信息:
data:节点存储的数据(字节数组,最大1MB)
acl:访问控制列表,定义访问权限
stat:节点状态信息
czxid:创建节点的事务IDmzxid:最后修改节点的事务IDctime:节点创建时间mtime:节点最后修改时间version:数据版本号cversion:子节点版本号aversion:ACL版本号ephemeralOwner:临时节点所有者会话IDdataLength:数据长度numChildren:子节点数量
客户端与Zookeeper服务器建立TCP连接后创建会话
会话有超时时间(sessionTimeout)
会话期间,客户端可以创建临时节点
会话超时后,临时节点会被删除
Watcher是Zookeeper的核心机制
客户端可以在ZNode上注册Watcher
当ZNode发生变化时,服务器会通知客户端
Watcher是一次性的,触发后需要重新注册
Zookeeper提供访问控制列表(ACL)来控制节点的访问权限:
- world:所有人都有权限
- auth:已认证的用户
- digest:用户名密码认证
- ip:IP地址认证
4. Zookeeper安装与配置
Zookeeper的配置文件是conf/zoo.cfg,主要配置项:
# 客户端连接端口
clientPort=2181
# 数据目录
dataDir=/opt/zookeeper/data
# 日志目录(可选)
dataLogDir=/opt/zookeeper/logs
# 心跳间隔(毫秒)
tickTime=2000
# 初始化连接超时时间(tickTime的倍数)
initLimit=10
# 同步超时时间(tickTime的倍数)
syncLimit=5
# 最大客户端连接数
maxClientCnxns=60
# 自动清理快照和日志文件(可选)
autopurge.snapRetainCount=3
autopurge.purgeInterval=14.2 单机模式配置
创建配置文件conf/zoo.cfg:
cd /opt/zookeeper
cp conf/zoo_sample.cfg conf/zoo.cfg编辑conf/zoo.cfg:
# 基本配置
tickTime=2000
dataDir=/opt/zookeeper/data
clientPort=2181
initLimit=5
syncLimit=2创建数据目录:
mkdir -p /opt/zookeeper/data4.3 启动Zookeeper
Linux/macOS启动
# 启动Zookeeper(前台运行)
bin/zkServer.sh start-foreground
# 启动Zookeeper(后台运行)
bin/zkServer.sh start
# 查看状态
bin/zkServer.sh status
# 停止Zookeeper
bin/zkServer.sh stop
# 重启Zookeeper
bin/zkServer.sh restartWindows启动
# 启动Zookeeper
bin\zkServer.cmd
# 或者双击zkServer.cmd文件# 方式1:检查进程
ps aux | grep zookeeper
# 方式2:检查端口
netstat -an | grep 2181
# 方式3:使用客户端连接
bin/zkCli.sh -server localhost:21815. Zookeeper命令行操作
# 连接本地Zookeeper
bin/zkCli.sh -server localhost:2181
# 连接远程Zookeeper
bin/zkCli.sh -server 192.168.1.100:2181
# 连接后显示提示符:[zk: localhost:2181(CONNECTED) 0]查看帮助
[zk: localhost:2181(CONNECTED) 0] help查看节点列表
# 查看根目录
[zk: localhost:2181(CONNECTED) 0] ls /
# 查看指定节点
[zk: localhost:2181(CONNECTED) 0] ls /zookeeper
# 递归查看(Zookeeper 3.5+)
[zk: localhost:2181(CONNECTED) 0] ls -R /创建节点
# 创建持久节点
[zk: localhost:2181(CONNECTED) 0] create /test "hello zookeeper"
# 创建临时节点
[zk: localhost:2181(CONNECTED) 0] create -e /temp "temporary data"
# 创建顺序节点
[zk: localhost:2181(CONNECTED) 0] create -s /seq "sequence data"
# 结果:Created /seq0000000001
# 创建临时顺序节点
[zk: localhost:2181(CONNECTED) 0] create -s -e /ephemeral_seq "data"获取节点数据
# 获取节点数据
[zk: localhost:2181(CONNECTED) 1] get /test
# 获取节点数据和状态信息
[zk: localhost:2181(CONNECTED) 2] get -s /test
# 只获取状态信息
[zk: localhost:2181(CONNECTED) 3] stat /test设置节点数据
# 设置节点数据
[zk: localhost:2181(CONNECTED) 4] set /test "updated data"
# 设置数据并指定版本号
[zk: localhost:2181(CONNECTED) 5] set /test "new data" 1删除节点
# 删除节点(节点必须没有子节点)
[zk: localhost:2181(CONNECTED) 6] delete /test
# 递归删除节点及其所有子节点
[zk: localhost:2181(CONNECTED) 7] deleteall /test
# 删除节点(指定版本号)
[zk: localhost:2181(CONNECTED) 8] delete /test 1监听节点变化
# 监听节点数据变化
[zk: localhost:2181(CONNECTED) 9] get -w /test
# 监听子节点变化
[zk: localhost:2181(CONNECTED) 10] ls -w /test5.3 实际操作示例
# 1. 连接Zookeeper
bin/zkCli.sh -server localhost:2181
# 2. 查看根目录
[zk: localhost:2181(CONNECTED) 0] ls /
# 输出:[zookeeper]
# 3. 创建测试节点
[zk: localhost:2181(CONNECTED) 1] create /app "application"
# 输出:Created /app
# 4. 创建子节点
[zk: localhost:2181(CONNECTED) 2] create /app/config "config data"
# 输出:Created /app/config
# 5. 查看节点树
[zk: localhost:2181(CONNECTED) 3] ls -R /
# 输出:
# /
# /app
# /app/config
# /zookeeper
# /zookeeper/config
# /zookeeper/quota
# 6. 获取节点数据
[zk: localhost:2181(CONNECTED) 4] get /app
# 输出:application
# 7. 设置节点数据
[zk: localhost:2181(CONNECTED) 5] set /app "updated application"
# 输出:cZxid = 0x2
# ctime = Mon Jan 15 10:00:00 CST 2024
# mZxid = 0x3
# mtime = Mon Jan 15 10:01:00 CST 2024
# ...
# 8. 删除节点
[zk: localhost:2181(CONNECTED) 6] delete /app/config
# 输出:已删除
# 9. 退出客户端
[zk: localhost:2181(CONNECTED) 7] quit6. Java客户端开发
Maven项目
在pom.xml中添加依赖:
<dependencies>
<!-- Zookeeper客户端 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.9.0</version>
</dependency>
<!-- 日志框架(可选) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>Gradle项目
在build.gradle中添加:
dependencies {
implementation 'org.apache.zookeeper:zookeeper:3.9.0'
implementation 'org.slf4j:slf4j-api:1.7.36'
implementation 'org.slf4j:slf4j-log4j12:1.7.36'
}基本连接
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.WatchedEvent;
public class ZookeeperClient {
private static final String CONNECT_STRING = "localhost:2181";
private static final int SESSION_TIMEOUT = 5000;
public static void main(String[] args) throws Exception {
// 创建Zookeeper客户端
ZooKeeper zk = new ZooKeeper(CONNECT_STRING, SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("收到事件:" + event.getType());
}
});
System.out.println("连接状态:" + zk.getState());
// 等待连接建立
Thread.sleep(2000);
System.out.println("连接状态:" + zk.getState());
// 关闭连接
zk.close();
}
}使用连接字符串
// 连接单个服务器
ZooKeeper zk1 = new ZooKeeper("localhost:2181", 5000, null);
// 连接多个服务器(逗号分隔)
ZooKeeper zk2 = new ZooKeeper("server1:2181,server2:2181,server3:2181", 5000, null);创建节点
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
public class ZookeeperOperations {
private ZooKeeper zk;
public ZookeeperOperations() throws Exception {
zk = new ZooKeeper("localhost:2181", 5000, null);
}
// 创建持久节点
public void createPersistentNode(String path, String data) throws Exception {
String createdPath = zk.create(path, data.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("创建节点:" + createdPath);
}
// 创建临时节点
public void createEphemeralNode(String path, String data) throws Exception {
String createdPath = zk.create(path, data.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println("创建临时节点:" + createdPath);
}
// 创建顺序节点
public void createSequentialNode(String path, String data) throws Exception {
String createdPath = zk.create(path, data.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
System.out.println("创建顺序节点:" + createdPath);
}
public void close() throws Exception {
zk.close();
}
}读取节点数据
// 读取节点数据
public String getNodeData(String path) throws Exception {
byte[] data = zk.getData(path, false, null);
return new String(data);
}
// 读取节点数据和状态
public void getNodeDataWithStat(String path) throws Exception {
Stat stat = new Stat();
byte[] data = zk.getData(path, false, stat);
System.out.println("数据:" + new String(data));
System.out.println("版本号:" + stat.getVersion());
System.out.println("创建时间:" + new Date(stat.getCtime()));
System.out.println("修改时间:" + new Date(stat.getMtime()));
}更新节点数据
import org.apache.zookeeper.data.Stat;
// 更新节点数据
public void updateNodeData(String path, String data) throws Exception {
Stat stat = zk.setData(path, data.getBytes(), -1);
System.out.println("更新成功,新版本号:" + stat.getVersion());
}
// 带版本号的更新(乐观锁)
public boolean updateNodeDataWithVersion(String path, String data, int version) {
try {
Stat stat = zk.setData(path, data.getBytes(), version);
System.out.println("更新成功");
return true;
} catch (KeeperException.BadVersionException e) {
System.out.println("版本冲突,更新失败");
return false;
}
}删除节点
// 删除节点
public void deleteNode(String path) throws Exception {
zk.delete(path, -1);
System.out.println("删除节点:" + path);
}
// 带版本号的删除
public void deleteNodeWithVersion(String path, int version) throws Exception {
zk.delete(path, version);
System.out.println("删除节点:" + path);
}列出子节点
import java.util.List;
// 列出子节点
public List<String> getChildren(String path) throws Exception {
List<String> children = zk.getChildren(path, false);
return children;
}
// 列出子节点并监听
public List<String> getChildrenWithWatch(String path) throws Exception {
List<String> children = zk.getChildren(path, true);
return children;
}import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.List;
public class ZookeeperExample {
private static final String CONNECT_STRING = "localhost:2181";
private static final int SESSION_TIMEOUT = 5000;
private ZooKeeper zk;
public void connect() throws Exception {
zk = new ZooKeeper(CONNECT_STRING, SESSION_TIMEOUT, event -> {
System.out.println("事件类型:" + event.getType());
System.out.println("事件路径:" + event.getPath());
});
// 等待连接建立
while (zk.getState() != ZooKeeper.States.CONNECTED) {
Thread.sleep(100);
}
System.out.println("连接成功");
}
public void createNode() throws Exception {
String path = "/test";
String data = "test data";
// 创建持久节点
String createdPath = zk.create(path, data.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("创建节点:" + createdPath);
}
public void readNode() throws Exception {
String path = "/test";
byte[] data = zk.getData(path, false, null);
System.out.println("节点数据:" + new String(data));
}
public void updateNode() throws Exception {
String path = "/test";
String newData = "updated data";
Stat stat = zk.setData(path, newData.getBytes(), -1);
System.out.println("更新成功,版本号:" + stat.getVersion());
}
public void deleteNode() throws Exception {
String path = "/test";
zk.delete(path, -1);
System.out.println("删除节点:" + path);
}
public void close() throws Exception {
zk.close();
}
public static void main(String[] args) {
ZookeeperExample example = new ZookeeperExample();
try {
example.connect();
example.createNode();
example.readNode();
example.updateNode();
example.readNode();
example.deleteNode();
example.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}7. Watcher监听机制
Watcher是Zookeeper的核心机制,允许客户端监听ZNode的变化。
- 一次性:Watcher触发后会被移除,需要重新注册
- 轻量级:只通知事件类型和路径,不包含具体数据
- 异步通知:事件通知是异步的
- 顺序保证:保证事件顺序与更新顺序一致
public class DataWatcher implements Watcher {
private ZooKeeper zk;
public DataWatcher() throws Exception {
zk = new ZooKeeper("localhost:2181", 5000, this);
}
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeDataChanged) {
System.out.println("节点数据发生变化:" + event.getPath());
try {
// 重新注册监听
byte[] data = zk.getData(event.getPath(), this, null);
System.out.println("新数据:" + new String(data));
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void watchNode(String path) throws Exception {
byte[] data = zk.getData(path, this, null);
System.out.println("初始数据:" + new String(data));
}
}public class ChildrenWatcher implements Watcher {
private ZooKeeper zk;
public ChildrenWatcher() throws Exception {
zk = new ZooKeeper("localhost:2181", 5000, this);
}
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeChildrenChanged) {
System.out.println("子节点发生变化:" + event.getPath());
try {
// 重新注册监听
List<String> children = zk.getChildren(event.getPath(), this);
System.out.println("子节点列表:" + children);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void watchChildren(String path) throws Exception {
List<String> children = zk.getChildren(path, this);
System.out.println("初始子节点:" + children);
}
}public class ExistenceWatcher implements Watcher {
private ZooKeeper zk;
public ExistenceWatcher() throws Exception {
zk = new ZooKeeper("localhost:2181", 5000, this);
}
@Override
public void process(WatchedEvent event) {
Event.EventType type = event.getType();
if (type == Event.EventType.NodeCreated) {
System.out.println("节点创建:" + event.getPath());
} else if (type == Event.EventType.NodeDeleted) {
System.out.println("节点删除:" + event.getPath());
}
// 重新注册监听
try {
zk.exists(event.getPath(), this);
} catch (Exception e) {
// 节点不存在,监听已注册
}
}
public void watchExistence(String path) throws Exception {
Stat stat = zk.exists(path, this);
if (stat != null) {
System.out.println("节点已存在");
} else {
System.out.println("节点不存在,等待创建");
}
}
}8. Zookeeper应用场景
场景:集中管理分布式系统的配置信息
public class ConfigManager {
private ZooKeeper zk;
private String configPath = "/app/config";
private Map<String, String> configCache = new ConcurrentHashMap<>();
public ConfigManager() throws Exception {
zk = new ZooKeeper("localhost:2181", 5000, event -> {
if (event.getType() == Event.EventType.NodeDataChanged) {
loadConfig();
}
});
loadConfig();
}
private void loadConfig() {
try {
byte[] data = zk.getData(configPath, true, null);
String config = new String(data);
// 解析配置并更新缓存
updateCache(config);
} catch (Exception e) {
e.printStackTrace();
}
}
public String getConfig(String key) {
return configCache.get(key);
}
}场景:实现分布式环境下的互斥访问
public class DistributedLock {
private ZooKeeper zk;
private String lockPath = "/locks";
private String lockName;
private String currentPath;
public DistributedLock() throws Exception {
zk = new ZooKeeper("localhost:2181", 5000, null);
// 确保锁目录存在
if (zk.exists(lockPath, false) == null) {
zk.create(lockPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
public boolean tryLock(String lockName) throws Exception {
this.lockName = lockName;
// 创建临时顺序节点
currentPath = zk.create(lockPath + "/" + lockName, null,
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// 获取所有锁节点
List<String> children = zk.getChildren(lockPath, false);
Collections.sort(children);
// 判断是否是最小的节点
String currentNode = currentPath.substring(lockPath.length() + 1);
int index = children.indexOf(currentNode);
if (index == 0) {
// 获得锁
return true;
} else {
// 监听前一个节点
String prevNode = lockPath + "/" + children.get(index - 1);
CountDownLatch latch = new CountDownLatch(1);
zk.exists(prevNode, event -> {
if (event.getType() == Event.EventType.NodeDeleted) {
latch.countDown();
}
});
latch.await();
return true;
}
}
public void unlock() throws Exception {
if (currentPath != null) {
zk.delete(currentPath, -1);
}
}
}8.3 Master选举
场景:选举集群中的主节点
public class MasterElection {
private ZooKeeper zk;
private String electionPath = "/election";
private String currentPath;
private boolean isLeader = false;
public MasterElection() throws Exception {
zk = new ZooKeeper("localhost:2181", 5000, event -> {
if (event.getType() == Event.EventType.NodeDeleted) {
electMaster();
}
});
if (zk.exists(electionPath, false) == null) {
zk.create(electionPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
electMaster();
}
private void electMaster() throws Exception {
// 创建临时顺序节点
currentPath = zk.create(electionPath + "/node", null,
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// 获取所有节点
List<String> children = zk.getChildren(electionPath, false);
Collections.sort(children);
// 判断是否是最小的节点
String currentNode = currentPath.substring(electionPath.length() + 1);
if (children.get(0).equals(currentNode)) {
isLeader = true;
System.out.println("成为Master");
onElectedAsMaster();
} else {
isLeader = false;
// 监听前一个节点
String prevNode = electionPath + "/" + children.get(children.indexOf(currentNode) - 1);
zk.exists(prevNode, true);
}
}
private void onElectedAsMaster() {
// Master节点的业务逻辑
System.out.println("执行Master任务");
}
public boolean isLeader() {
return isLeader;
}
}场景:作为服务注册中心
public class ServiceRegistry {
private ZooKeeper zk;
private String registryPath = "/services";
public ServiceRegistry() throws Exception {
zk = new ZooKeeper("localhost:2181", 5000, null);
if (zk.exists(registryPath, false) == null) {
zk.create(registryPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
// 注册服务
public void registerService(String serviceName, String serviceAddress) throws Exception {
String servicePath = registryPath + "/" + serviceName;
if (zk.exists(servicePath, false) == null) {
zk.create(servicePath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
String addressPath = servicePath + "/" + serviceAddress;
zk.create(addressPath, serviceAddress.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println("服务注册成功:" + serviceName + " -> " + serviceAddress);
}
// 发现服务
public List<String> discoverService(String serviceName) throws Exception {
String servicePath = registryPath + "/" + serviceName;
if (zk.exists(servicePath, false) == null) {
return Collections.emptyList();
}
List<String> addresses = zk.getChildren(servicePath, false);
return addresses;
}
}9. Zookeeper集群部署
Zookeeper集群通常由奇数个节点组成(3、5、7等),采用主从架构:
- Leader:处理写请求,发起投票
- Follower:处理读请求,参与投票
- Observer:处理读请求,不参与投票(可选)
配置文件
每个节点的zoo.cfg:
# 基本配置
tickTime=2000
dataDir=/opt/zookeeper/data
clientPort=2181
initLimit=5
syncLimit=2
# 集群配置
server.1=192.168.1.101:2888:3888
server.2=192.168.1.102:2888:3888
server.3=192.168.1.103:2888:3888配置说明:
server.X:X是服务器ID(1、2、3...)IP:2888:Leader和Follower通信端口IP:3888:选举端口
创建myid文件
在每个节点的dataDir目录下创建myid文件:
# 节点1
echo "1" > /opt/zookeeper/data/myid
# 节点2
echo "2" > /opt/zookeeper/data/myid
# 节点3
echo "3" > /opt/zookeeper/data/myid9.3 启动集群
# 在每个节点上启动Zookeeper
bin/zkServer.sh start
# 查看状态
bin/zkServer.sh status
# 应该显示:Mode: leader 或 Mode: follower# 连接到集群
bin/zkCli.sh -server 192.168.1.101:2181,192.168.1.102:2181,192.168.1.103:2181
# 创建测试节点
create /test "cluster test"
# 连接到另一个节点验证
# 数据应该是一致的10. Zookeeper最佳实践
数据量小:每个节点数据不超过1MB
读多写少:适合读多写少的场景
临时节点:用于服务发现和心跳检测
顺序节点:用于分布式锁和队列
合理设置sessionTimeout:不要设置过小
使用连接池:复用Zookeeper连接
批量操作:减少网络往返
异步操作:使用异步API提高性能
使用ACL:设置合适的访问权限
网络隔离:限制Zookeeper端口访问
定期备份:备份数据目录
监控告警:监控集群状态
10.4 常见问题避免
- 避免频繁创建删除节点
- 避免在根目录创建大量节点
- 避免存储大文件
- 避免长时间阻塞Watcher回调
11. 常见问题与解决方案
问题:无法连接到Zookeeper
解决方案:
- 检查Zookeeper是否启动
- 检查端口是否正确(默认2181)
- 检查防火墙设置
- 检查网络连接
问题:频繁出现Session expired
解决方案:
- 增加sessionTimeout时间
- 检查网络稳定性
- 实现重连机制
问题:节点不存在异常
解决方案:
- 先检查节点是否存在
- 使用exists方法
- 处理KeeperException.NoNodeException
问题:Watcher没有触发
解决方案:
- Watcher是一次性的,需要重新注册
- 在Watcher回调中重新注册
- 使用Curator框架简化Watcher管理
12. 总结与进阶
通过本教程,你已经掌握了:
- ✅ Zookeeper的基本概念和特性
- ✅ Zookeeper的安装和配置
- ✅ 命令行操作和Java客户端开发
- ✅ Watcher监听机制
- ✅ 常见应用场景的实现
- ✅ 集群部署和最佳实践
- Curator框架:Zookeeper的高级客户端
- ZAB协议:深入了解Zookeeper的一致性协议
- 性能调优:JVM参数和Zookeeper配置优化
- 监控运维:使用JMX和监控工具
- 源码阅读:理解Zookeeper的实现原理
- 官方文档:https://zookeeper.apache.org/doc/current/
- Curator文档:https://curator.apache.org/
- GitHub:https://github.com/apache/zookeeper
- 搭建集群:实践集群部署
- 实现应用:实现分布式锁、配置管理等
- 性能测试:测试Zookeeper的性能
- 故障演练:模拟节点故障,观察集群行为
结语
Zookeeper是分布式系统中非常重要的基础设施,掌握Zookeeper对于分布式开发至关重要。通过本教程的学习,相信你已经掌握了Zookeeper的核心功能和使用方法。
记住:
- 多实践:理论结合实践,多写代码
- 理解原理:深入理解Zookeeper的工作原理
- 关注性能:注意性能优化和最佳实践
- 持续学习:关注Zookeeper社区和新技术
祝你学习愉快,编程顺利! 🚀
本教程由Java突击队学习社区编写,如有问题欢迎反馈。