一、ZooKeeper入门
ZooKeeper是一个分布式的,开源的协调服务,是Hadoop的重要组件之一,用于维护配置信息、命名、提供分布式同步和组服务等。ZooKeeper通常被用作构建大型分布式系统的基础工具,例如Kafka、HBase等知名项目都在内部使用了ZooKeeper来进行节点间的协调工作。
简介:Apache ZooKeeper是一种用于分布式应用程序的高性能协调服务。提供一种集中式信息存储服务。
特点:数据存在内存中,类似文件系统的树形结构(文件和目录),高吞吐量和低延迟,集群高可靠。
作用:基于zookeeper可以实现分布式统一配置中心、服务注册中心,分布式锁等功能的实现;
官网地址:https://zookeeper.apache.org/ 官网开发指南:https://zookeeper.apache.org/doc/r3.9.3/zookeeperProgrammers.html
zooKeeper特性:
- 顺序一致性(Sequential Consistency):保证客户端操作是按顺序生效的。
- 原子性(Atomicity):更新成功或失败。没有部分结果。
- 集群强一致性:无论连接到哪个服务器,客户端都将看到相同的内容
- 可靠性:数据的变更不会丢失,除非被客户端覆盖修改。(通过事务日志文件)
- 及时性:保证系统的客户端当时读取到的数据是最新的。
1.1、数据结构规范
ZooKeeper的数据结构类似Unix文件系统树形结构,每个目录称为Znode节点,但是又不同于文件系统,既可以做目录拥有子节点,又可以做文件存放数据。
节点的路径需要遵循以下限制条件:
- 同一节点下的子节点名称不能相同
- 命名规范:
- null字符(\u0000)不能作为路径名的一部分;
- 以下字符不能使用,因为它们不能很好地显示,或者以令人困惑的方式呈现:\u0001 - \u0019和\u007F - \u009F;
- 不允许使用以下字符:\ud800 - uf8fff, \uFFF0 - uFFFF;
- “.”字符可以用作另一个名称的一部分,但是“.”和“..”不能单独用于指示路径上的节点,因为ZooKeeper不使用相对路径;
- “zookeeper”是保留节点名;
- 绝对路径:节点的路径总是表示为规范的、绝对的、斜杠分隔的路径,不能使用相对路径
- 存放的数据大小有限制:1M
详细可以查看:官方文档
1.2、ZooKeeper节点
ZooKeeper树中的每个节点都被称为znode。Znode维护一个状态结构,该结构包含数据更改、acl(访问控制列表)更改的版本号、时间戳等。版本号与时间戳一起允许ZooKeeper验证缓存并协调更新。每次znode的数据发生变化时,版本号就会增加。例如,每当客户端检索数据时,它也会接收到数据的版本。当客户端执行更新或删除操作时,它必须提供它正在更改的znode的数据版本。如果它提供的版本与数据的实际版本不匹配,更新将会失败。官方文档
Znode的数据是以原子方式读取和写入
1.2.1、状态结构
ZooKeeper中每个znode(节点)的状态结构由以下字段组成:
- czxid:创建ID。
- mzxid:最后修改ID。
- pzxid:子节点最后修改的zxid。
- ctime:该节点的创建时间(毫秒数)。
- mtime:最后修改时间(最后一次被修改时距纪元的毫秒数)。
- version:数据被修改的次数。
- cversion:子节点被修改的次数。
- aversion:访问控制列表(ACL)被修改的次数。
- ephemeralOwner:临时节点所有者的会话ID,如果不是临时节点数据是0,如果是临时节点则为当前的会话ID。
- dataLength:数据字段的长度。
- numChildren:子节点数量。
1.2.2、节点类型
- 持久节点(PERSISTENT):一旦创建后,除非显式调用API进行删除,否则该节点将一直存在。
- 临时节点(EPHEMERAL):与客户端会话绑定,当会话结束时(例如客户端断开连接或崩溃)该节点会被自动删除;此外,临时节点不允许有子节点。可以使用API中的getEphEmerals()方法检索会话的临时列表。
- 顺序节点(SEQUENTIAL):无论是持久还是临时节点都可以设置为顺序节点,在创建时会在路径后面附加一个递增的序列号。该序列号对于父 znode 是唯一的。该序列号的格式为 %010d—— 即 10 位数字,带有 0(零)填充(计数器以这种方式格式化以简化排序),即 “0000000001”。
- 容器节点:在ZooKeeper 3.5.3版本中引入了容器节点(Container Znodes)的概念。容器节点是一种特殊用途的znode,适用于诸如领导者(leader)、锁(lock)等场景。当容器节点的最后一个子节点被删除时,该容器节点将在未来的某个时刻成为服务器删除的候选对象。鉴于这一特性,在容器节点内创建子节点时,您应当做好捕获 KeeperException.NoNodeException 异常的准备。
- TTL节点:在 ZooKeeper 3.5.3 版本中,引入了 TTL 节点(Time-To-Live Nodes)的功能。当创建 PERSISTENT 或 PERSISTENT_SEQUENTIAL 类型的 znode 时,您可以选择为该 znode 设置一个以毫秒为单位的 TTL(生存时间)。
1.2.3、Znode节点权限
ZooKeeper使用ACL(访问控制列表)来控制对其znode的访问。与标准UNIX权限不同的是,ZooKeeper没有znode所有者的概念,且子节点不会继承父节点的ACL,每个节点必须单独设置ACL。临时节点的ACL与会话绑定,会话结束后自动删除。
每个ACL由以下三部分组成:
- Scheme(认证模式):定义认证机制的类型。
- world:默认模式,表示所有用户。
- auth:已认证的用户。
- digest:基于用户名和密码的认证。
- ip:基于IP地址的认证。
- sasl:基于Kerberos的认证。
- x509:基于TLS/SSL证书的认证。
- ID(标识):表示被授权的实体,具体内容取决于Scheme。
- world:ID固定为 anyone。
- auth:ID为空,表示当前已认证的用户。
- digest:ID为 username:password 的Base64编码。
- ip:ID为IP地址或网段(如 192.168.1.100 或 192.168.1.0/24)。
- sasl 和 x509:ID为用户名或证书标识。
- Permissions(权限):定义允许的操作。
- CREATE:创建子节点。
- READ:读取节点数据和子节点列表。
- WRITE:修改节点数据。
- DELETE:删除子节点。
- ADMIN:设置ACL。
如果没有显式设置ACL,ZooKeeper会使用默认ACL:world:anyone,cdrwa,表示默认情况下,任何用户都可以对节点进行任何操作。
ACL的常见场景示例
1. 基于用户名和密码的认证(Digest)
使用 digest Scheme,可以为特定用户设置权限。例如:
create /app/config "data" digest:user1:password1:cdrwa
客户端连接时需要提供用户名和密码:
addauth digest user1:password1
2. 基于IP地址的认证(IP)
使用 ip Scheme,可以限制只有特定IP地址的客户端可以访问节点。例如:
create /app/config "data" ip:192.168.1.100:cdrwa
只有IP为 192.168.1.100 的客户端可以访问该节点。
3. 基于SASL的认证
使用 sasl Scheme,可以集成Kerberos等认证机制。例如:
create /app/config "data" sasl:user1:cdrwa
4. 匿名访问(World)
使用 world Scheme,允许所有用户访问节点。例如:
create /app/config "data" world:anyone:r
上述命令允许所有用户读取节点数据,但不能执行其他操作。
1.3、ZooKeeper的时序
ZooKeeper可以通过多种方式跟踪时间。官方文档
- Zxid(事务ID):ZooKeeper中的每次更改操作都对应一个唯一的事务id,称为Zxid,它是一个全局有序的戳记。
如果zxid1小于zxid2,则zxid1发生在zxid2之前。
- Version numbers:版本号,对节点的每次更改都会导致该节点的版本号之一增加。
- dataVersion:对znode数据的更改次数
- cversion:对znode子节点的更改次数
- aclVersion:对znode ACL的更改次数
- Ticks:当使用多服务器ZooKeeper时,服务器使用计时节拍来定义事件的时间,如状态上传、会话超时、
对等点之间的连接超时等。计时节拍时间仅通过最小会话超时(计时节拍的2倍)间接体现;如果客户端请求的会话超时小于最小会话超时,服务器将告诉客户端会话超时实际上是最小会话超时。
- Real time:ZooKeeper除了在znode创建和修改时将时间戳放入stat结构之外,根本不使用Real time或时钟时间。
1.4、ZooKeeper的会话机制
ZooKeeper的会话机制是其分布式协调功能的核心基础,通过会话ID、超时机制、心跳机制等实现了客户端与服务器之间的可靠连接。官方文档
- 一个客户端连接一个会话,由zk分配唯一会话id。
- 客户端以特定的时间间隔(tickTime)发送心跳以保持会话有效。
- 超过会话超时时间未收到客户端的心跳,则判定客户端死了。(默认2倍tickTime)
- 会话中的请求按FIFO(先进先出)顺序执行。
会话在其生命周期中会经历以下几种状态:
- CONNECTING:客户端正在尝试连接ZooKeeper服务器。
- CONNECTED:客户端成功连接到ZooKeeper服务器,会话处于活动状态。
- CLOSED:会话已关闭,客户端与服务器的连接终止。
- EXPIRED:会话已过期,通常是由于客户端在超时时间内未能与服务器通信。
本地会话(Local session)
背景:在ZooKeeper中,会话的创建和关闭成本很高,因为它们需要法定人数的确认。当ZooKeeper集群需要处理数千个客户端连接时,这会成为瓶颈。因此,在3.5.0版本之后,ZooKeeper引入了一种新的会话类型:本地会话它不具备普通(全局)会话的全部功能。通过开启localSessionsEnabled即可使用此功能。
本地会话(local session)主要是为了应对在高并发场景下,传统会话创建和关闭成本过高成为性能瓶颈的问题而设计的。
本地会话也可以升级为全局会话
当localSessionsUpgradingEnabled被禁用时:本地会话无法创建临时节点。当本地会话连接时,会话信息仅保存在其所连接的Zookeeper服务器上。领导者(leader)不会知晓此类会话的创建,并且没有状态写入磁盘。会话的心跳(pings)、过期处理以及其他会话状态维护由当前会话所连接的服务器负责。
当localSessionsUpgradingEnabled被启用时:
- 本地会话可自动升级为全局会话。
- 创建新会话时,它会在一个封装的LocalSessionTracker中本地保存。之后可根据需要(例如创建临时节点)将其升级为全局会话。如果请求升级,会话将从本地集合中移除,但会话ID保持不变。
- 目前,只有创建临时节点这一操作需要从本地会话升级为全局会话。原因是临时节点的创建在很大程度上依赖于全局会话。如果本地会话无需升级为全局会话就能创建临时节点,将会导致不同节点之间的数据不一致。领导者也需要了解会话的生命周期以便在关闭/过期时清理临时节点。这需要一个全局会话,因为本地会话与其特定的服务器绑定。
- 在升级期间,一个会话既可以是本地会话也可以是全局会话,但升级操作不能被两个线程同时调用。
1.5、Watch监听机制
客户端可以在znodes上设置watch,以监听znode的变化。
watch主要分为 数据监听(data watch)和子节点变化监听(child watch)。
1.5.1、标准监听
触发事件:原生的客户端有如下3个方法:getData()、getChildren()、exists(),getData()和exists()可以设置数据相关操作的监视;getChildren()可以设置子节点相关操作的监视。
- 创建事件:通过调用exists来启用。
- 删除事件:通过调用exists、getData和getChildren来启用。
- 变更事件:通过调用exists和getData来启用。
- 子节点事件:通过调用getChildren来启用。
Watch的重要特性
- 一次性触发:watch触发后即被删除。要持续监控变化,则需要持续设置watch;
- 有序性:ZooKeeper提供了一个顺序保证:客户端先得到watch通知,后才会看到变化结果。
Watch的注意事项
- watch是一次性触发器;如果您获得了一个watch事件,并且希望得到关于未来更改的通知,则必须设置另一个watch。
- 因为watch是一次性触发器,并且在获取事件和发送获取watch的新请求之间存在延迟,所以不能可靠地得到节点发生的每个更改。
- 一个watch对象只会被特定的通知触发一次。如果一个watch对象同时注册了exists、getData,当节点被删除时,删除事件对exists 、getData都有效,但只会调用watch一次。(也就是只会发送一个watch请求给客户端,这个请求会触发exists和getData两个方法。)
1.5.2、持久化监听
ZooKeeper 3.6.0 引入的持久递归监视功能增强了客户端对节点变化的监控能力,使其能够更灵活地处理各种场景下的数据变化需求。通过合理使用持久监视,可以提高分布式系统的可靠性和响应能力。
标准的监听在触发一次后会被自动移除。而持久监听则不同,它在触发后不会被移除,可以持续监听节点的变化。
持久监听的特点:
- 不自动移除: 持久监听在触发后不会被自动移除,因此可以持续监听节点的变化。
- 支持多种事件类型: 这些监听器可以监听 NodeCreated、NodeDeleted 和 NodeDataChanged 事件。
- 递归监听: 可以选择性地对注册监听器的 znode 下的所有子节点(znodes)进行递归监听。
- 不触发 NodeChildrenChanged 事件: 由于持久监听已经涵盖了子节点的变化,因此不会触发 NodeChildrenChanged 事件,以避免冗余。
使用方法:
设置持久监听:使用 addWatch() 方法来设置一个持久监听。例如:
zooKeeper.exists("/path/to/znode", watcher, Watcher.Event.KeeperState.SyncConnected);
通过传递适当的 Watcher 实例,可以指定监听的事件类型和行为。
移除持久监听:使用 removeWatches() 方法,并指定 WatcherType.Any 来移除所有类型的监听器。例如:
zooKeeper.removeWatches("/path/to/znode", WatcherType.Any, false, this);
注意事项:
- 触发语义和保证: 除了监听器不会在一次触发后被自动移除外,其他触发语义和保证与标准监听相同。
- 递归监听的限制: 由于持久监听已经覆盖了子节点的变化,因此递归持久监听器不会触发 NodeChildrenChanged 事件,以避免不必要的重复通知。
1.6、ZK和Eureka的区别
- ZK的设计原则是CP,即强一致性和分区容错性。他保证数据的强一致性,但舍弃了可用性,如果出现网络问题可能会影响 ZK 的选举,导致 ZK 注册中心的不可用。
- Eureka的设计原则是AP,即可用性和分区容错性。他保证了注册中心的可用性,但舍弃了数据一致性,各节点上的数据有可能是不一致的(会最终一致)。
二、ZooKeeper的使用
2.1、ZooKeeper的命令介绍
提供了一组丰富的命令行工具,用于管理和操作 ZooKeeper 集群中的数据。这些命令可以帮助用户创建、删除、查看和修改 znodes(ZooKeeper 数据节点),以及执行各种管理任务。以下是一些常用的 ZooKeeper 命令及其说明:
1. 连接到 ZooKeeper 服务器:zkCli.sh -server <host>:<port>
zkCli.sh -server localhost:2181
2. 创建 znode:create <path> <data> [acl] [flags]
create /my-node "Hello ZooKeeper" world:anyone:crwda
3. 查看 znode 数据:get <path> [watch]
get /my-node
4. 列出 znode 的子节点:ls <path> [watch]
ls /my-node
5. 更新 znode 数据:set <path> <data> [version]
set /my-node "Updated Data" 1
6. 删除 znode:delete <path> [version]
delete /my-node
7. 递归删除 znode 及其子节点:rmr <path>
rmr /my-node
8. 查看 znode 的详细信息:stat <path> [watch]
stat /my-node
9. 检查 znode 是否存在:exists <path> [watch]
exists /my-node
10. 设置 ACL(访问控制列表):setAcl <path> <acl>
setAcl /my-node world:anyone:crwda
11. 获取 ACL:getAcl <path>
getAcl /my-node
12. 创建临时 znode:create -e <path> <data> [acl] [flags]
create -e /temp-node “Temporary Data”
13. 创建顺序编号的 znode:create -s <path> <data> [acl] [flags]
create -s /sequential-node “Sequential Data”
14. 执行事务:ZooKeeper 支持原子性的事务操作,可以同时执行多个命令。
multi
create /tx-node1 "Data1"
create /tx-node2 "Data2"
commit
15. 查看帮助信息:help
help create
16. 退出 ZooKeeper 客户端:quit
2.2、ZK原生客户端
maven仓库:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.1</version>
</dependency>
核心方法:
- connect - 连接到ZooKeeper集合
- create- 创建znode
- exists- 检查znode是否存在及其信息
- getData - 从特定的znode获取数据
- setData - 在特定的znode中设置数据
- getChildren - 获取特定znode中的所有子节点
- delete - 删除特定的znode
- close - 关闭连接
缺点:
- 接口难以使用
- 在连接zk超时,不支持自动重连
- Watch注册一次会失效,需要反复注册
- 不支持递归创建和删除节点
- 需要手动序列化
2.2、第三方客户端Zkclient
其基于原生API改造,比原生API用起来更顺手。
Maven仓库:
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
CreateMode创建模式,节点类型
- PERSISTENT:持久节点
- PERSISTENT_SEQUENTIAL:持久顺序节点
- EPHEMERAL:临时节点
- EPHEMERAL_SEQUENTIAL:临时顺序节点
解决了原生客户端客户端的哪些问题?
- Watcher自动重注册:这个要是依赖于hasListeners()的判断。
- Session失效重连:如果发现会话过期,就先关闭已有连接,再重新建立连接。
- 异常处理:对比ZooKeeper和ZKClient,就可以发现ZooKeeper的所有操作都是抛异常的,而ZKClient的所有操作,都不会抛异常的。在发生异常时,它或做日志,或返回空,或做相应的Listener调用。
2.3、第三方客户端Curator
Curator Apache 的开源项目,功能更加丰富
- 解决Watch注册一次就会失效的问题
- 连接重连
- 提供的 API 更加简单易用
- 提供更多解决方案并且实现简单,例如:分布式锁
- 提供常用的ZooKeeper工具类
- 编程风格更舒服
- Curator中还提供了Zookeeper各种应用场景(Recipe,如共享锁服务、Master选举机制和分布式计算器等)的抽象封装。
maven仓库:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
全部评论