zk为分布式应用提供了高效且可靠的分布式协调服务。用于解决数据同步、数据发布/订阅、集群管理、配置管理、分布式锁、命名服务、负载均衡等问题。最早由雅虎创建,具有以下特性:
- 顺序一致性
- 原子性
- 单一视图(不管连接到zk集群的哪台机器,客户端看到的视图都是一致的)
- 可靠性
- 实时性
zk采用树型结构的名字空间,类似于一个文件系统的目录结构,全量数据存储在内存中。当节点发生变化时(创建、删除、数据变更),可以通知各个客户端。
zk的数据节点有两种:持久性节点、临时节点。对于临时节点,一旦创建znode的客户端与服务器失去联系,这个znode就会自动删除,ZooKeeper的客户端与服务器通信采用长连接的方式(这种连接状态称为session),如果znode是临时节点,这个session失效,znode也就被删除了。
zk以集群形式对外提供服务,集群只要有一半的机器能正常工作,就可以正常运转。默认端口号2181。
zk支持单机、集群两种模式。
常用于一些大型的分布式系统的应用,比如:Hadoop、Kafka、Hbase、dubbo
常用命令:
- 创建节点(create)
- 读取(get)
- 更新节点的数据内容(set)
- 删除(delete)
开源的客户端:
- ZkClient
- Curator
ZkClient是github上一个开源的ZK客户端,在zookeeper原生API接口之上进行了包装,是一个更易用的ZK客户端。实现了如session超时重连、Watcher反复注册等功能,使得zookeeper客户端的繁琐细节工作对开发人员透明。
pom依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.5</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>jline</groupId>
<artifactId>jline</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
/**
* 使用ZKClient来创建一个Zookeeper客户端
*
* @author onlyone
*/
public class CreateSession {
public static void main(String[] args) {
// 建立连接
ZkClient zkClient = new ZkClient("192.168.1.1:2188,192.168.1.2:2188;192.168.1.3:2188", 5000);
}
}
ZkClient 构造方法参数说明:
参数 | 描述 |
---|---|
zkServers | 用英文状态的逗号分开的host:port字符串组成,比如 192.168.1.1:2188,192.168.1.2:2188;192.168.1.3:2188 |
connectionTimeout | 创建连接超时时间,单位毫秒,超时无法与ZooKeeper建立连接,抛出异常 |
sessionTimeout | 会话超时时间,单位毫秒,默认 30000 |
ZkSerializer | 自定义序列化器 |
zkConnection | IZkConnection接口实现类,包含了增、删、改、查等一系列方法 |
参数 | 描述 |
---|---|
path | 指定数据节点的节点路径 |
data | 节点的初始数据内容,可以为null |
mode | 节点类型,可以为一个枚举类型,有4种 |
createParents | 是否创建父节点 |
// 创建provier子节点,如果父节点root不存在,会先创建父节点
zkClient.createPersistent("/root/provier", true);
参数 | 描述 |
---|---|
path | 数据节点的完整节点路径 |
// zk只允许叶子节点,如果下面还有子节点的话,无法直接删除,deleteRecursive方法自动逐层遍历删除节点
zkClient.deleteRecursive("/root/provier");
- getChildren
//获取指定节点的子节点列表,返回值为子节点的相对路径,结果 [provider,consumer]
List<String> subNode = zkClient.getChildren("/root");
-
subscribeChildChanges
与原生的Watcher不同的是,zkclient的Listener不是一次性的,客户端只需要注册一次就会一直生效。
// 订阅子节点列表变更的监听,一旦子节点列表发生变更,zk服务端会向客户端发出事件通知,由Listener来处理
zkClient.subscribeChildChanges("/root", new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
System.out.println("parentPath=" + parentPath + ",currentChilds=" + currentChilds);
}
});
-
readData
获取指定节点的数据内容。
参数 | 描述 |
---|---|
returnNullIfPathNotExists | 如果节点不存在,返回null,而不是抛出异常 |
stat | 指定数据节点的节点状态信息 |
-
subscribeDataChanges
“节点内容变更”和“节点删除”事件监听。
zkClient.subscribeDataChanges("/root", new IZkDataListener() {
@Override
public void handleDataDeleted(String dataPath) throws Exception {
// 指定节点被删除,dataPath为全路径
System.out.println("Node: " + dataPath + " deleted!");
}
// 指定节点的数据内容或数据版本发生变更,会触发这个事件。
// dataPath为全路径;data最新的数据节点内容
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
System.out.println("Node: " + dataPath + " changed!, new data:" + data);
}
});
参数 | 描述 |
---|---|
path | 数据节点的完整路径 |
object | 数据内容,可以是null |
expectedVersion | 预期的数据版本,实现类似CAS的原子操作 |
boolean isNodeExist = zkClient.exists("/root");