简介
数据模型:
Zookeeper 通过树形结构来存储数据
- 树中节点类型
| 创建命令 | 生命周期 | 顺序性 | 子节点 | 适用场景 | |
|---|---|---|---|---|---|
| 持久节点 | create /path data | 永久存在 | 无 | 支持 | 配置存储、元数据 |
| 临时节点 | create -e /path data | 会话结束删除 | 无 | 不支持 | 服务发现、分布式锁 |
| 持久顺序节点 | create -s /path data | 永久存在 | 有序(节点名称拼接一个10位序号,zk内部用完就不允许再创建,基本用不完) | 支持 | 任务队列、命名序列 |
| 临时顺序节点 | create -e -s /path data | 会话结束删除 | 有序 | 不支持 | 公平锁、领导者选举 |
| 容器节点 | create -c /path data | 自动清理/后代无节点后会在特定的时机被zk自动清理 | 可选 | 专门存放 | 工作组模式 |
| TTL节点 | create -t ttl /path data | 超时删除 | 可选 | 支持 | 临时缓存 |
-
节点信息
状态属性 说明 czxid 数据节点创建时的事务 ID ctime 数据节点创建时的时间 mzxid 数据节点最后一次更新时的事务 ID mtime 数据节点最后一次更新时的时间 pzxid 数据节点的子节点最后一次被修改时的事务 ID cversion 子节点的更改次数 version 节点数据的更改次数 aversion 节点的 ACL 的更改次数 ephemeralOwner 如果节点是临时节点,则表示创建该节点的会话的 SessionID;如果节点是持久节点,则该属性值为 0 dataLength 数据内容的长度 numChildren 数据节点当前的子节点个数 -
节点权限ACL
CREATE:允许创建子节点; READ:允许从节点获取数据并列出其子节点; WRITE:允许为节点设置数据; DELETE:允许删除子节点; ADMIN:允许为节点设置权限。
高可用:zookeeper集群保证只要集群中半数以上机器正常工作整个集群就能正常工作,通过zab算法保证分布式集群的可用
顺序性: 每个写操作交由leader节点来执行都会分配一个id,能够保证集群内所有节点的写操作顺序一致
高性能:写操作交由leader执行,读操作任意节点都可以执行,非常适合读多写少场景,监听机制使得客户端不用轮询
集群概念:
- leader : 组织写
- follower : 读写服务
- Observer : 只读节点
会话: 每个客户端都会和集群中的某个节点建立tcp长连接
应用:
- 基于顺序节点实现分布式锁
- 用于管理集群:比如作为集群的注册中心(dubbo)
Linux安装
- 去官网下载对应的二进制版本
- linux解压
- 进入解压目录的conf目录,就在目录里面拷贝一份zoo_sample.cfg然后改名为zoo.cfg
修改配置文件
# 默认服务端口8080
admin.serverPort=8088
# 修改数据目录
dataDir=/home/ekko/Public/apache-zookeeper-3.9.4-bin/data
# 可以修改客户端连接的端口 配置文件里面默认为2181
clientPort=2181
启动:进入解压目录的bin目录
./zkServer.sh start
没有报错就启动成功,输入jps 能看到QuorumPeerMain进程说明启动成功
# 检查是否启动成功
./zkServer.sh status
zookeeper提供的客户端连接zookeeper,然后就可以通过命令行操作zookeeper
./zkCli.sh -server 127.0.0.1:2181
基本操作
命令行基本操作
| 数据操作 | get /path | 获取指定节点的数据和详细信息(如事务ID、时间戳等)。 |
|---|---|---|
set /path "newData" | 更新指定节点的数据。 | |
delete /path | 删除没有子节点的指定节点。 | |
rmr /path | 递归删除节点及其所有子节点。 | |
| 辅助功能 | stat /path | 查看指定节点的状态信息(不返回节点数据本身)。 |
quit 或 close | 退出ZooKeeper客户端。 |
java操作zk
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.5</version>
</dependency>
public class Main {
//所有的api都在Zookeeper对象中,使用说明的注释也都很清晰
private static ZooKeeper zk;
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
initZk();
// ACL 访问权限 createMode 节点类型
zk.create("/ekko/e", "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 用于接收返回的节点状态
Stat stat = new Stat();
byte[] data = zk.getData("/ekko/e", new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("已经触发了" + event.getType() + "事件!");
}
}, stat);
System.out.println(new String(data));
System.out.println(stat);
// -1表示任意版本号
zk.create("/ekko/e2", "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
zk.delete("/ekko/e2",-1);
for (int i = 0; i < 9; i++) {
zk.create("/ekko/e", "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
}
List<String> children = zk.getChildren("/ekko", false);
for (int i = 0; i < children.size(); i++) {
System.out.println(children.get(i));
}
Thread.sleep(10000);
}
private static void initZk() throws IOException {
zk = new ZooKeeper("192.168.7.223:2181", 10000, event->{
// getType() 是枚举类型
System.out.println("已经触发了" + event.getType() + "事件!");
});
}
}
使用zookeeper实现的分布式锁的一种思路:zookeeper集群会保证命令执行的顺序性
- 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
使用持久带TTL的顺序节点,创建节点,然后获取所有节点,并监听(exists命令)比自己大的序号的节点的状态,删除节点时说明获取锁成功。 - 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,通常使用版本号机制或CAS(Compare and Swap)操作。
如果我们操作的zookeeper节点数据,那么可以用节点版本号来进行乐观锁的判断,更新的时候携带版本号。 - 可重入锁:也称为递归锁,指的是同一个线程在外层方法获取锁之后,内层递归方法仍然可以获取该锁。在分布式环境中,我们需要记录锁的持有者以及重入次数。
可以通过线程变量实现,每次尝试加锁都先从线程变量中获取锁,获取成功就不用再次加锁,需要维护一个加锁次数。 - 不可重入锁:与可重入锁相反,同一个线程在已经持有锁的情况下再次请求锁时会阻塞。在分布式环境中,我们只需要判断当前线程是否已经持有锁,如果已经持有则阻塞或报错。
- 公平锁:多个客户端按照申请锁的顺序来获取锁,遵循先来先服务(FCFS)的原则
- 非公平锁:允许客户端在锁可用时立即获取,而不考虑它们的请求顺序
先尝试在某个路径创建一个固定名称的节点,创建成功说明加锁成功,创建失败则监听这个路径是否有节点删除,缺点是这种方式在竞争激烈的情况下会触发羊群效应(接收到通知后,只有一个请求有效,其余请求都是无效的,然而zk却需要处理所有请求)