目录
1.功能简介
前言
zookeeper主要用于协调分布式服务、 配置维护、组合管理和命名服务,通过使用类似于文件操作系统的目录结构树来维护元数据。参考官网 https://zookeeper.apache.org/
本文主要介绍zk的主要功能,命令行、数据结构和Java API编程demo
1.功能
集群管理 | 作为集群的入口,可以查找集群的存活主机列表 |
服务注册 | dubbo zk作为注册中心,服务提供者将服务注册到Zookeeper, 客户端在调用服务之前先到Zookeeper中查找服务,再调用服务。 |
分布式锁 | 在多个主机并发捞取任务,为了仅一台及其执行,通过获取分布式锁控制仅一台及其执行任务 |
配置文件集中管理 | 统一管理分布式环境中的配置文件 |
...... | ...... |
2.安装
从官方下载后, 解压后即可使用
- 服务配置 /${安装路径}/zookeeper-3.4.14/conf/zoo.cfg,可以配置如clientPort=2181
- 服务启动 cd /${安装路径}/soft/zookeeper-3.4.14/bin &&sh zkServer.sh start
- 客户端连接 sh zkCli.sh -server localhost:2181
3.ZKshell操作命令
通过sh /${安装路径}/soft/zookeeper-3.4.14/bin/zkCli.sh -server localhost:2181 可以进入zookeeper命令行模式(类似于mysql和redis 客户端连接)
其实ZK就是一个树形数据结构,存放在内存汇总,提供了API可以对这颗树进行查找节点、修改节点的值等等操作。
追根到底就是对数据的增删改查。
命令 | 操作 |
ls path [watch] | 查看路径的子节点, 不过不存在返回Node does not exist: /test |
create path data acl | 创建节点,父path必须存在 类似与创建一个目录 |
get path [watch] | 去一个节点上存储的data值 |
set path data [version] | 设置节点的值 |
delete path [version] | 删除节点 子节点为空 |
deleteall path | 删除所有包括子节点 |
stat path [watch] | 查看节点描述元信息 |
4.基础概念
Znode数据节点、Watcher监听器、ACL访问控制立标、会话(Session)
4.1 Znode
上节有说zk是基于一个树形数据结构,我们都知道树由各个树节点组成,数据节点一一ZNode。如上图,结构特别想linux操作系统的文件系统设计,区别就是zk的将该树存储在内存,而linux存储在磁盘。存储在内存的问题就是会在zk服务重启后原来的数据易丢失,及持久化问题。
每个节点上都会保存自己的数据内容,同时还会保存一系列属性信息,Zxid(Transaction Id),version修改次数(版本)等等。
4.1.1 Stat属性
在前面我们已经提到,对于每个ZNode,Zookeeper 都会为其维护一个叫作 Stat 的数据结构,Stat 中记录了这个 ZNode 的属性。
以下是通过zk命令行创建并修改节点后查到的属性
create /test testdata #创建节点
set /test testdata2 #修改节点
create /test/son son #创建子节点
get /test #查看节点数据和属性
属性 | 样例 | 含义 |
cZxid | 0x2 |
创建该节点的事务ID |
ctime | Thu Nov 21 11:10:21 CST 2019 |
创建时间 |
mZxid | 0x3 |
最后修改该节点的事务ID (set /test testdata2) |
mtime | Thu Nov 21 11:13:28 CST 2019 |
最后修改时间 |
pZxid | 0x4 |
|
cversion | 1 |
子节点children版本号(创建了一个自节点) |
dataVersion | 1 |
数据修改版本 |
aclVersion | 0 | 访问控制版本 |
ephemeralOwner | 0x0 |
if 临时节点,该znode的所有者的会话ID。 else,为零。 |
dataLength | 9 |
字节长度 |
numChildren | 1 |
子节点个数 |
树节点按照持久性维度可以分为持久节点和临时节点,区别在于 临时节点在客户端断开连接后会被删除。临时节点由ephemeralOwner标识,非0的是临时节点。
节点还有一种 顺序节点(SEQUENTIAL),会在指定path后面附加一个序号(递增),如创建顺序节点zk_test,会创建一个节点path=zk_test0000000001
String perPath = zk .create("zk_test", "data", ZooDefs.Ids.OPEN_ACL_UNSAFE , CreateMode.PERSISTENT_SEQUENTIAL);
4.2 Watcher
事件通知机制是Zookeeper实现分布式协调服务的重要特性,客户端client可以在指定节点上注册一些Watcher(事件监听器),并且在一些特定事件触发的时候,zk服务端Server会将事件通知到注册事件的客户端,客户端可以定义回调函数针对事件做一些处理。类似于Listener和观察者模式。
事件 | 触发原因 | 接受事件操作 |
创建事件 | 当前节点被创建 | exist() |
删除事件 | 当前节点被删除 | |
更改事件 | 当前节点data数据被修改 | exist() && getData |
子节点事件 | 自节点数组有改动 | getChildren() |
4.3 ACL
Zookeeper采用ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。Zookeeper 定义了如下5种权限。
- CREATE: 创建自节点
- READ: 读取节点上的数据及其子节点列表
- WRITE:设置节点上的数据
- DELETE: 删除子节点
- ADMIN: 设置ACL权限
4.4 会话(Session)
类似与httpSession,基于TCP连接之上,保存了一些C-S客户-服务端交互的状态。服务端分配sessionID,服务端用此id为key存储了一些客户端信息。
会话开始:客户端启动的时候,会与zk服务端建立TCP长连接,第一次连接时服务端为客户端分配一个sessionID,会话的生命周期开始。
会话保持:客户端通过心跳检测与服务器保持有效会话,能够向服务器发送请求并接受响应,同时还能接收服务器的Watch事件通知。
会话销毁:客户端设置sessionTimeout属性-
会话超时时间,当tcp连接断开
,只要在sessionTimeout
时间内重新连接上集群中任意一台服务器,会话仍然有效,当超过此时间还不能重连会话就失效销毁了。
5.编程demo
5.1 连接到ZooKeeper
// 连接 ZooKeeper 服务器
private ZooKeeper connectServer() {
ZooKeeper zk = null;
try {
zk = new ZooKeeper(Constant.ZK_CONNECTION_STRING, Constant.ZK_SESSION_TIMEOUT ,
new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event .getState() == Event.KeeperState.SyncConnected ) {
// 唤醒当前正在执行的线程
latch.countDown();
}
}
});
// 使当前线程处于等待状态
latch.await();
} catch (IOException | InterruptedException e) {
logger.error("" , e );
}
return zk ;
}
5.2 读取操作
- exists 读取节点属性
- getDate 读取节点数据
- getChildren 读取节点子节点
// 2. 读取操作 exist,getData和getChildren
private void readDataFormZK(ZooKeeper zooKeeper, String path) {
try {
Stat curState = zooKeeper.exists(path, false);
logger.info("attr of " + path +" is " + curState.toString());//attr of /test is 2,3,1574305821415,1574306008185,1,1,0,0,9,1,4
byte[] data = zooKeeper.getData(path, false, new Stat());
logger.info("data of " + path +" is " +new String(data));//data of /test is testdata2
List<String> children = zooKeeper.getChildren(path, false);
logger.info("children of " + path +" is " +children.toString());//children of /test is [son]
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void read() {
ZooKeeper zk = connectServer();
String path = "/test";
readDataFormZK(zk, path);
}
5.3 写操作
@Test
public void create() {
ZooKeeper zk = connectServer();
try {
String dataStr = "test";
byte[] data = dataStr.getBytes();
//1. 创建一个临时性且有序的 ZNode 其余客户端看不到
String tempPath = zk .create(Constant.ZK_TEST_PATH, data, ZooDefs.Ids.OPEN_ACL_UNSAFE , CreateMode.EPHEMERAL_SEQUENTIAL);
//2. 创建一个持久顺序节点
String perPath = zk .create(Constant.ZK_TEST_PATH, data, ZooDefs.Ids.OPEN_ACL_UNSAFE , CreateMode.PERSISTENT_SEQUENTIAL);
logger.info("create ephemeral zookeeper node ({} => {})" , tempPath , dataStr );//create ephemeral zookeeper node (/zk_test0000000020 => test)
logger.info("create persistent zookeeper node ({} => {})" , perPath , dataStr );//create persistent zookeeper node (/zk_test0000000021 => test)
//3. 修改数据
Stat current=zk.setData(tempPath,"setDate".getBytes(),0);
logger.info("new attr of "+tempPath+" from zookeeper is {}.", current);//new attr of /zk_test0000000020 from zookeeper is 72,74,1574321648638,1574321648649,1,0,0,72058819008200718,7,0,72
logger.info("new date of "+tempPath+" from zookeeper is {}.", new String(zk.getData(tempPath, false, current)));//new date of /zk_test0000000020 from zookeeper is setDate.
//4. 添加子节点 NoChildrenForEphemerals 临时节点下不能有临时节点
String childPath = zk.create(perPath+"/child", "child".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
logger.info("child added :" + childPath);//child added :/zk_test0000000021/child
} catch (KeeperException | InterruptedException e) {
logger.error("" , e );
}
}
6.数据一致特点
ZooKeeper读取和写入操作都很快速,读取比写入快,因为读取的时候可以用较旧的数据,类似与mysql的MVCC多版本控制。ZooKeeper提供了以下一致性保证:
- 顺序一致性:来自客户端的更新操作将按照发送的顺序执行。
- 原子性:更新全部成功或全部失败
- 单个系统映像:无论客户端连接到哪个服务器(集群节点),客户端都将看到该服务的相同视图。
- 可靠性:一旦应用了更新,该更新将一直持续到客户端覆盖该更新为止。
- 此保证有两个推论:
如果客户获得成功的返回码,则将应用更新。在某些故障(通信错误,超时等)上,客户端将不知道更新是否已应用。我们会采取措施以最大程度地减少失败,但是只有成功的返回码才能提供保证。 (这在Paxos中称为单调性条件。)
从服务器故障中恢复时,客户端通过读取请求或成功更新看到的任何更新都不会回滚。
及时性:保证系统的客户视图在特定的时间范围内(大约数十秒)是最新的。客户端可以在此范围内看到系统更改,或者客户端将检测到服务中断。
7.应用场景实例
7.1 集群管理
集群的入口 ,如kafka,会将自己的集群节点 写入到zk的/brokers/ids,客户端可以根据brokers/ids下面的临时节点1,获取存活的一个主机进行通信。以下是/brokers/ids/1的内容,可以发现此节点是临时节点
get /brokers/ids/1 // 获取节点数据和属性
// 数据
{
"listener_security_protocol_map": {
"PLAINTEXT": "PLAINTEXT"
},
"endpoints": [
"PLAINTEXT://${ip}:9092"
],
"jmx_port": -1,
"host": "${ip}",
"timestamp": "1574238458689",
"port": 9092,
"version": 4
}
// 属性
cZxid = 0x2b
ctime = Wed Nov 20 16:27:38 CST 2019
mZxid = 0x2b
mtime = Wed Nov 20 16:27:38 CST 2019
pZxid = 0x2b
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x1000575bc430006
dataLength = 192
numChildren = 0
连接Kafka集群仅需要提供zk的地址就可以获取kafka的ip:host列表。上图可以发现这个kafka工具客户端就是读取zk的/brokers/ids。
7.2 服务注册
如dubbo中,可以用zookeeper作为注册中心Registry
如果服务注册中心配置成zookeeper,那服务提供者会在zookeeper上创建/dubbo/demo.DemoService ,发布了服务,
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:service interface="demo.DemoService" ref="demoService"/>
该节点下有[consumers, configurators, routers, providers]
查看服务提供者 ls /dubbo/demo.DemoService/providers
此时只有一个提供者,包含了ip和端口,还有接口、方法、进程号等信息,消费者可以从该服务中心获取这些信息,可以发起RPC调用或者其它方式的调用。
[dubbo://${ip}:20880/demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.6.2&generic=false&interface=demo.DemoService&methods=sayHello&pid=16376&side=provider×tamp=1574407126770]