ZooKeeper 介绍与使用


1. zookeeper是什么?

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

2. zookeeper能做什么?

Apache ZooKeeper是由集群(节点组)使用的一种服务,用于在自身之间协调,并通过稳健的同步技术维护共享数据。ZooKeeper本身是一个分布式应用程序,为写入分布式应用程序提供服务。

zooKeeper提供的常见服务如下 :

  • 命名服务 - 按名称标识集群中的节点。它类似于DNS,但仅对于节点。

  • 配置管理 - 加入节点的最近的和最新的系统配置信息。

  • 集群管理 - 实时地在集群和节点状态中加入/离开节点。

  • 选举算法 - 选举一个节点作为协调目的的leader。

  • 锁定和同步服务 - 在修改数据的同时锁定数据。此机制可帮助你在连接其他分布式应用程序(如Apache HBase)时进行自动故障恢复。

  • 高度可靠的数据注册表 - 即使在一个或几个节点关闭时也可以获得数据。

分布式应用程序提供了很多好处,但它们也抛出了一些复杂和难以解决的挑战。ZooKeeper框架提供了一个完整的机制来克服所有的挑战。竞争条件和死锁使用故障安全同步方法进行处理。另一个主要缺点是数据的不一致性,ZooKeeper使用原子性解析。

zooKeeper的好处

以下是使用zooKeeper的好处:

  • 简单的分布式协调过程
  • 同步 - 服务器进程之间的相互排斥和协作。此过程有助于Apache HBase进行配置管理。
  • 有序的消息
  • 序列化 - 根据特定规则对数据进行编码。确保应用程序运行一致。这种方法可以在MapReduce中用来协调队列以执行运行的线程。
  • 可靠性
  • 原子性 - 数据转移完全成功或完全失败,但没有事务是部分的。

3. zookeeper 基础

zooKeeper的架构

zooKeeper的架构
作为ZooKeeper架构的一部分的每个组件在下表中进行了说明。

部分 描述
Client(客户端) 客户端,我们的分布式应用集群中的一个节点,从服务器访问信息。对于特定的时间间隔,每个客户端向服务器发送消息以使服务器知道客户端是活跃的。类似地,当客户端连接时,服务器发送确认码。如果连接的服务器没有响应,客户端会自动将消息重定向到另一个服务器。
Server(服务器) 服务器,我们的ZooKeeper总体中的一个节点,为客户端提供所有的服务。向客户端发送确认码以告知服务器是活跃的。
Ensemble ZooKeeper服务器组。形成ensemble所需的最小节点数为3。
Leader 服务器节点,如果任何连接的节点失败,则执行自动恢复。Leader在服务启动时被选举。
Follower 跟随leader指令的服务器节点。

层次命名空间

下图描述了用于内存表示的ZooKeeper文件系统的树结构。ZooKeeper节点称为 znode 。每个znode由一个名称标识,并用路径(/)序列分隔。

  • 在图中,首先有一个由“/”分隔的znode。在根目录下,你有两个逻辑命名空间 config 和 workers 。
  • config 命名空间用于集中式配置管理,workers 命名空间用于命名。
  • 在 config 命名空间下,每个znode最多可存储1MB的数据。这与UNIX文件系统相类似,除了父znode也可以存储数据。这种结构的主要目的是存储同步数据并描述znode的元数据。此结构称为 ZooKeeper数据模型。

Znode兼具文件和目录两种特点。既像文件一样维护着数据长度、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分。每个Znode由三个部分组成:

  • stat:此为状态信息,描述该Znode版本、权限等信息。
  • data:与该Znode关联的数据
  • children:该Znode下的节点
     
  • 版本号 - 每个znode都有版本号,这意味着每当与znode相关联的数据发生变化时,其对应的版本号也会增加。当多个zookeeper客户端尝试在同一znode上执行操作时,版本号的使用就很重要。
  • 操作控制列表(ACL)- ACL基本上是访问znode的认证机制。它管理所有znode读取和写入操作。
  • 时间戳 - 时间戳表示创建和修改znode所经过的时间。它通常以毫秒为单位。ZooKeeper从“事务ID"(zxid)标识znode的每个更改。Zxid 是唯一的,并且为每个事务保留时间,以便你可以轻松地确定从一个请求到另一个请求所经过的时间。
  • 数据长度 - 存储在znode中的数据总量是数据长度。你最多可以存储1MB的数据。

Znode的类型

Znode被分为持久(persistent)节点,顺序(sequential)节点和临时(ephemeral)节点。

  • 持久节点 - 即使在创建该特定znode的客户端断开连接后,持久节点仍然存在。默认情况下,除非另有说明,否则所有znode都是持久的。
  • 临时节点 - 客户端活跃时,临时节点就是有效的。当客户端与ZooKeeper集合断开连接时,临时节点会自动删除。因此,只有临时节点不允许有子节点。如果临时节点被删除,则下一个合适的节点将填充其位置。临时节点在leader选举中起着重要作用。
  • 顺序节点 - 顺序节点可以是持久的或临时的。当一个新的znode被创建为一个顺序节点时,ZooKeeper通过将10位的序列号附加到原始名称来设置znode的路径。例如,如果将具有路径 /myapp 的znode创建为顺序节点,则ZooKeeper会将路径更改为 /myapp0000000001 ,并将下一个序列号设置为0000000002。如果两个顺序节点是同时创建的,那么ZooKeeper不会对每个znode使用相同的数字。顺序节点在锁定和同步中起重要作用。
  • 容器节点(3.6新增的):ZooKeeper具有容器节点的概念。容器节点是特殊用途的节点,可用于诸如集群中的leader,分布式锁等场景。当删除容器节点的最后一个子节点时,也就是容器节点没有子节点之后,该容器节点会在未来某个时间被服务器删除。
  • TTL节点(3.6新增的):在创建持久化或者持久化有序节点时,可以把节点设置为TTL节点。并指定TTL的时间,单位为毫秒。如果接在在TTL时间内未修改且没有子代,该节点会在未来某个时间被服务器删除。该属性默认是禁用的,需要额外配置才能启用。

Sessions(会话)

会话对于ZooKeeper的操作非常重要。会话中的请求按FIFO顺序执行。一旦客户端连接到服务器,将建立会话并向客户端分配会话ID 。
客户端以特定的时间间隔发送心跳以保持会话有效。如果ZooKeeper集合在超过服务器开启时指定的期间(会话超时)都没有从客户端接收到心跳,则它会判定客户端死机。
会话超时通常以毫秒为单位。当会话由于任何原因结束时,在该会话期间创建的临时节点也会被删除。

Watches(监视)

监视是一种简单的机制,使客户端收到关于ZooKeeper集合中的更改的通知。客户端可以在读取特定znode时设置Watches。Watches会向注册的客户端发送任何znode(客户端注册表)更改的通知。
Znode更改是与znode相关的数据的修改或znode的子项中的更改。只触发一次watches。如果客户端想要再次通知,则必须通过另一个读取操作来完成。当连接会话过期时,客户端将与服务器断开连接,相关的watches也将被删除。

4. zookeeper 安装

4.1 单机安装

4.1.1 安装JDK

ZooKeeper服务器是用Java创建的,它在JVM上运行。你需要使用JDK 6或更高版本。linux安装jdk8

# 查看jdk版本
java -version

4.1.2 下载安装zookeeper

# 下载对应版本安装包
wget http://mirrors.bfsu.edu.cn/apache/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz

# 解压到指定安装目录
tar -zxvf apache-zookeeper-3.6.2-bin.tar.gz -C /usr/local/zookeeper

# 创建数据和日志目录
cd /usr/local/zookeeper && mv apache-zookeeper-3.6.2-bin apache-zookeeper-3.6.2
mkdir -pv data logs

# 设置自己的配置文件
vi /usr/local/zookeeper/apache-zookeeper-3.6.2/conf/zoo.cfg

4.1.3 zookeeper配置文件

# 心跳间隔 毫秒
tickTime=1000

# 启动时leader连接follower,超过多少次心跳间隔,follower连接超时
initLimit=10
# leader与follower 通信超时 最长心跳间隔次数
syncLimit=5

# 客户端连接的端口
clientPort=2181
# 客户端连接的最大数量
#maxClientCnxns=60

# 数据文件目录
dataDir=/usr/local/zookeeper/apache-zookeeper-3.6.2/data
# 日志目录
dataLogDir=/usr/local/zookeeper/apache-zookeeper-3.6.2/logs

# 最小会话超时时间
# minSessionTimeout=2000
# 最大会话超时时间
# maxSessionTimeout=20000

# 清除任务间隔(以小时为单位)
# 设置为0以禁用自动清除功能
autopurge.purgeInterval=5
# 最多保存20个文件 日志文件、快照
autopurge.snapRetainCount=20 

#开启四字命令
#4lw.commands.whitelist=*

# 集群信息
# server.A=B:C:D
# A 代表记号服务器,在dataDir目录下myid文件下记录
# B 服务器ip地址
# C 服务器与集群中的leader服务器交换信息的端口
# D 选举leader所用的端口
# server.1=127.0.0.1:3181:4181

4.1.4 常用shell操作命令

  • 启动zk : bin/zkServer.sh start
  • 查看ZK服务状态: bin/zkServer.sh status
  • 停止ZK服务: bin/zkServer.sh stop
  • 重启ZK服务: bin/zkServer.sh restart
  • 连接服务器 : bin/zkCli.sh -server 127.0.0.1:2181

4.1.5 启动并测试服务:

# 启动服务
./bin/zkServer.sh start

# 启动客户端测试
./bin/zkCli.sh

4.2 单机伪集群

使用一台服务器,创建多个服务实现zookeeper伪集群。

4.2.1 创建集群目录结构

这里创建clester目录,为每个服务指定目录。
data是数据目录,conf是配置文件目录,logs是日志目录。

# 创建每个服务对应的目录 这里使用2181,2182,2183三个端口 
mkdir -pv cluster/{2181/{conf,data,logs},2182/{conf,data,logs},2183/{conf,data,logs}}

4.2.2 指定服务机器名称

zookeeper集群中的每台机器都知道其他的机器的信息,便于在集群出现问题后,重新选举leader等。

# 指定服务机器名称
echo 1 > cluster/2181/data/myid
echo 2 > cluster/2182/data/myid
echo 3 > cluster/2183/data/myid

4.2.3 创建服务配置文件

为每个服务配置指定的配置文件,主要修改端口号,数据、日志目录。

# 进入2181服务目录,修改其配置文件
vi cluster/2181/conf/zoo.cfg

# 更改端口号,数据、日志目录,并配置集群机器信息
clientPort=2181
dataDir=/usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2181/data
dataLogDir=/usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2181/logs

server.1=127.0.0.1:3181:4181
server.2=127.0.0.1:3182:4182
server.3=127.0.0.1:3183:4183

这里是单机伪集群,通信端口和选举端口都是不一样的,正式使用不用机器,可以是不同ip和统一端口号。

# 复制配置文件到其他服务目录
cp cluster/2181/conf/zoo.cfg cluster/2182/conf/zoo.cfg
cp cluster/2181/conf/zoo.cfg cluster/2183/conf/zoo.cfg

# 修改其他服务配置文件 
vi cluster/2182/conf/zoo.cfg
vi cluster/2182/conf/zoo.cfg

# vim 快速替换 %s/2181/2182/g

4.2.4 启动集群服务

使用集群不同服务目录配置文件,启动对应服务。

./bin/zkServer.sh start /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2181/conf/zoo.cfg
./bin/zkServer.sh start /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2182/conf/zoo.cfg
./bin/zkServer.sh start /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2183/conf/zoo.cfg

4.2.5 查看服务状态

./bin/zkServer.sh status /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2181/conf/zoo.cfg
./bin/zkServer.sh status /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2182/conf/zoo.cfg
./bin/zkServer.sh status /usr/local/zookeeper/apache-zookeeper-3.6.2/cluster/2183/conf/zoo.cfg

通过status可以查看服务的状态,我们启动三个服务,有一个leader和两个follower。
zookeeper服务状态
可以看到2182是leader,2181和2183是follower。
我们尝试把2182(leader)停止,再去查看状态,可以看到重新选举了2183为leader,2182重新启动后会成为follower。
zookeeper服务状态

5. zookeeper CLI

ZooKeeper命令行界面(CLI)用于与ZooKeeper集合进行交互以进行开发。它有助于调试和解决不同的选项。
要执行ZooKeeper CLI操作,首先打开ZooKeeper服务器(“bin/zkServer.sh start”),然后打开ZooKeeper客户端(“bin/zkCli.sh”)。
命令帮助

5.1 节点属性

属性名 属性说明
cZxid 数据节点创建的事务id
cZxid 数据节点创建的事务id。
ctime 数据节点创建的时间。
mZxid 数据节点最后一次更新时的事务id
mtime 数据节点最后一次更新的时间。
pZxid 数据节点的子节点最后一次被修改时的事务id。
cversion 子节点的更改次数。
dataVersion 节点数据的更改次数,也就是数据的版本。类似于关系型数据库的乐观锁。
aclVersion 节点ACL权限的更改次数。
ephemeralOwner 如果节点是临时节点,则该属性表示创建该节点的会话SessionId。如果该节点是持久节点,该属性为0,可以使用该属性来判断节点是否为临时节点。
dataLength 数据的内容长度,单位字节。
numChildren 子节点的个数。

zxid:致使ZooKeeper节点状态改变的每一个操作都将使节点接收到一个递增的事务id号,并且这个时间戳全局有序。也就是说,也就是说,每个对节点的改变都将产生一个唯一的Zxid。如果Zxid1的值小于Zxid2的值,那么Zxid1所对应的事件发生在Zxid2所对应的事件之前。实际上,ZooKeeper的每个节点维护者三个Zxid值,为别为:cZxid、mZxid、pZxid。

5.2 常用操作

介绍关于节点的常用操作,包括节点的增删改查,以及配额、同步等。

# 创建一个节点 -e为临时节点 -s为顺序节点 -c为容器节点 -t为ttl节点
create /file "file data"

# 获取当前节点数据
get /file

# 设置节点数据
set /file "data"

# 创建子节点
create /file/test "data"

# 获取当前节点状态
stat /file

# 删除一个节点
delete /file
# 删除指定版本的节点
delete -v 1 /file

# 删除节点以及其包含的子节点
deleteall /file

# 设置节点配置
# -n 子节点个数 -b 节点数据长度
# 超出配额并不会报错,而是会在日志(*.out)中出现警告
setquota /data -n 3
setquota /data -b 100

# 查看配额
listquota /data

# 删除配额
delquota /data

# 列出节点下面的所有子节点
ls -R /

# 强制同步所有的更新操作
sync /data

其他命令:

close:关闭当前连接
history:查看历史执行指令

redo命令
再次执行某命令。
如redo 10
其中10为命令ID,需与history配合使用。

sync
由于请求在半数以上的zk server上生效就表示此请求生效,那么就会有一些zk server上的数据是旧的。sync命令就是强制同步所有的更新操作。

printwatches
在获取节点数据、子节点列表等操作时,都可以添加watch参数监听节点的变化,从而节点数据更改、子节点列表变更时收到通知,并输出到控制台。默认是打开,可以设置参数将其关闭。
printwatches

6. 运维四字命令

zookeeper可以通过它自身提供的简写命令来和服务器进行交互,需要使用到nc命令.

# 安装命令
yum install nc

在配置文件中开启四字命令

4lw.commands.whitelist=*

查看配置信息:
nc 配置信息

conf
conf命令用于输出 ZooKeeper服务器运行时使用的基本配置信息,包括clientPort、dataDir和tickTime等,以便运维人员能快速查看 ZooKeeper当前运行时的一些参数,如上图所示。注意,conf命令输出的配置信息仅仅是输出一些最基本的配置参数。
另外,conf命令会根据当前的运行模式来决定输出的信息。上图所示的输出信息是针对集群模式下的样例,如果是单机模式(standalone),就不会输出诸如initLimit、syncLimit、electionAlg和electionPort等集群相关的配置信息。

cons
cons命令用于输出当前这台服务器上所有客户端连接的详细信息,包括每个客户端的客户端IP、会话ID和最后一次与服务器交互的操作类型等。

crst
crst命令是一个功能性命令,用于重置所有的客户端连接统计消息

dump
dump命令用于输出当前集群的所有会话信息,包括这些会话的会话ID,以及每个会话创建的临时节点等信息。如果在Leader服务器上执行该命令的话,我们还能够看到每个会话的超时时间。

envi
envi命令用于输出 ZooKeeper所在服务器运行时的环境信息,包括os.version、java.version和user.home等。

ruok
ruok命令用于输出当前 ZooKeeper服务器是否正在运行。该命令的名字非常有趣,其协议正好是“Are you ok”。执行该命令后,如果当前 ZooKeeper服务器正在运行,那么返回“imok”,否则没有任何响应输出。
请注意,ruok命令的输出仅仅只能表明当前服务器是否正在运行,准确的讲,只能说明2181端口打开着,同时四字命令执行流程正常,但是不能代表 ZooKeeper服务器是否运行正常。在很多时候,如果当前服务器无法正常处理客户端的读写请求,甚至已经无法和集群中的其他机器进行通信,ruok命令依然返回“imok”。因此,一般来说,该命令并不是一个特别有用的命令,他不能反映 ZooKeeper服务器的工作状态,想要更可靠的获取更多 ZooKeeper运行状态信息,可以使用下面马上要讲到的stat命令。

stat
stat命令用于获取 ZooKeeper服务器的运行时状态信息,包括基本的 ZooKeeper版本、打包信息、运行时角色、集群数据节点个数等消息,另外还会将当前服务器的客户端连接信息打印出来。
除了一些基本的状态信息外,stat命令还会输出一些服务器的统计信息,包括延迟情况、收到请求数和返回的响应数等。注意,所有这些统计数据都可以通过srst命令进行重置。

srvr
srvr命令和stat命令的功能一致,唯一的区别是srvr不会将客户端的连接情况输出,仅仅输出服务器的自身信息

srst
stst命令是一个功能行命令,用于重置所有服务器的统计信息。

wchs
wchs命令用于输出当前服务器上管理的Watcher的概要信息。

wchc
wchc命令用于输出当前服务器上管理的Watcher的详细信息,以会话为单位进行归组,同时列出被该会话注册了Watcher的节点路径。

wchp**
wchp命令和wchc命令非常类似,也是用于输出当前服务器上管理的Watcher的详细信息,不同点在于wchp命令的输出信息以节点路径为单位进行归组。

mntr
mntr命令用于输出比stat命令更为详尽的服务器统计信息,包括请求处理的延迟情况,服务器内存数据库大小和集群的数据同步情况。在输出结果中,每一行都是一个key-value的键值对,运维人员可以,根据这些输出信息进行 ZooKeeper的运行时状态监控。

如果在Leader服务器上执行该命令的话,可以获取比Follower服务器更多的信息。

7. ACL权限

ACL全称为Access Control List(访问控制列表),用于控制资源的访问权限。ZooKeeper使用ACL来控制对其znode(ZooKeeper数据树的数据节点)的访问。ACL实现与UNIX文件访问权限非常相似:它使用权限位来允许/禁止针对节点的各种操作以及位应用的范围。与标准UNIX权限不同,ZooKeeper节点不受用户(文件所有者),组和world(其他)的三个标准范围的限制。

zk利用ACL策略控制节点的访问权限,如节点数据读写、节点创建、节点删除、读取子节点列表、设置节点权限等。

在传统的文件系统中,一个文件拥有某个组的权限即拥有了组里的所有权限,文件或子目录默认会继承自父目录的ACL。而在Zookeeper中,znode的ACL是没有继承关系的,每个znode的权限都是独立控制的,只有客户端满足znode设置的权限要求时,才能完成相应的操作。

Zookeeper的ACL,分为三个维度:scheme、id、permission,通常表示为:scheme🆔permission,schema代表授权策略,id代表用户,permission代表权限。下面分别讲述一下这三个属性:

7.1 scheme

scheme即采取的授权策略,每种授权策略对应不同的权限校验方式。

  • world:默认方式,相当于全世界都能访问
  • auth:不使用任何id,表示任何经过身份验证的用户。
  • digest:即用户名:密码这种方式认证,这也是业务系统中最常用的,使用username:password字符串生成MD5哈希,然后将其用作ACL的ID标识。通过以明文形式发送 例如:wangsaichao:123456 来完成身份验证。在ACL中使用时,表达式将是wangsaichao:G2RdrM8e0u0f1vNCj/TI99ebRMw=。
  • ip:使用Ip地址认证

7.2 id

id是验证模式,不同的scheme,id的值也不一样。scheme为digest时,id的值为:username:BASE64(SHA1(password)),scheme为ip时,id的值为客户端的ip地址。scheme为world时,id的值为anyone。scheme为auth时,id为 username:password。

7.3 permission

CREATE、READ、WRITE、DELETE、ADMIN 也就是 增、删、改、查、管理权限,这5种权限简写为crwda。
这5种权限中,delete是指对子节点的删除权限,其它4种权限指对自身节点的操作权限。

  • CREATE(c):创建子节点的权限
  • DELETE(d):删除节点的权限
  • READ(r):读取节点数据的权限
  • WRITE(w):修改节点数据的权限
  • ADMIN(a):设置子节点权限的权限
    注意:
    要修改某个节点的ACL属性,必须具有read、admin二种权限。
    要删除某个节点下的子节点,必须具有对父节点的read权限,以及父节点的delete权限。

7.4 相关命令

命令 语法 介绍
getAcl getAcl 读取ACL权限
setAcl setAcl 设置ACL权限
addauth addauth 添加认证用户

7.6 使用介绍

7.6.1 super

一旦我们为某一个节点设置了acl,那么其余的未授权的节点是无法访问或者操作该节点的,那么系统用久了以后,假如忘记了某一个节点的密码,那么就无法再操作这个节点了,所以需要这个super超级管理员用户权限,其作用还是很大的。

在启动服务的时候,添加管理员账号

# 获取加密后的账号信息root:root
[root@VM-0-12-centos apache-zookeeper-3.6.2]# echo -n root:root | openssl dgst -binary -sha1 | openssl base64
qiTlqPLK7XM2ht3HMn02qRpkKIE=

# 修改bin下面的zkServer.sh
# 找到 nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \ 这一行,大概在158行
# 在后面添加一句
"-Dzookeeper.DigestAuthenticationProvider.superDigest=root:qiTlqPLK7XM2ht3HMn02qRpkKIE=" \

# 完成的命令如下
nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \
"-Dzookeeper.DigestAuthenticationProvider.superDigest=root:qiTlqPLK7XM2ht3HMn02qRpkKIE=" \
"-Dzookeeper.log.file=${ZOO_LOG_FILE}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \

# 重新启动服务,登录
addauth digest root:root

7.6.2 word

语法:world:anyone:cdrwa
创建节点默认的scheme,所有人都可以访问。

# 创建一个节点
[zk: 127.0.0.1:2181(CONNECTED) 10] create /node01 "data"
Created /node01

# 查看ACL
[zk: 127.0.0.1:2181(CONNECTED) 11] getAcl /node01
'world,'anyone
: cdrwa

# 设置所有人可以读
setAcl /node01 world:anyone:r

# 此时只有r权限,写操作会失败
[zk: localhost:2181(CONNECTED) 17] set /test a
Authentication is not valid : /test

# 添加认证用户-管理员 即可完成set
addauth digest root:root

7.6.3 digest

语法:digest:username:BASE64(SHA1(password)):cdrwa

digest:是授权方式
username:BASE64(SHA1(password)):是id部分
cdrwa:权限部份

用户名+密码授权访问方式,也是常用的一种授权策略。
id部份是用户名和密码做sha1加密再做BASE64加密后的组合,比如设置一个节点的用户名为user,密码为123456,则表示方式为:
原:user:BASE64(SHA1(user:123456))
正确:user:6DY5WhzOfGsWQ1XFuIyzxkpwdPo=。

使用lunix加密

[root@VM-0-12-centos ~]# echo -n user:123456 | openssl dgst -binary -sha1 | openssl base64
6DY5WhzOfGsWQ1XFuIyzxkpwdPo=

使用zookeeper工具类加密

[root@VM-0-12-centos ~]# java -cp /usr/local/zookeeper/apache-zookeeper-3.6.2/lib/zookeeper-3.6.2.jar:/usr/local/zookeeper/apache-zookeeper-3.6.2/lib/slf4j-api-1.7.25.jar org.apache.zookeeper.server.auth.DigestAuthenticationProvider user:123456
user:123456->user:6DY5WhzOfGsWQ1XFuIyzxkpwdPo=

使用java代码进行加密

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.11</version>
</dependency>
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.7.0</version>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.6.2</version>
</dependency>
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import org.junit.Test;
import java.security.MessageDigest;
import org.apache.commons.codec.binary.Base64;

public class test {

    /**
     * 使用第三方包生成
     * @throws Exception
     */
    @Test
    public void test1() throws Exception{

        String usernameAndPassword = "user:123456";
        byte digest[] = MessageDigest.getInstance("SHA1").digest(usernameAndPassword.getBytes());
        Base64 base64 = new Base64();
        String encodeToString = base64.encodeToString(digest);
        System.out.println(encodeToString);

    }

    /**
     * 使用zookeeper提供的方法生成
     * @throws Exception
     */
    @Test
    public void test2() throws Exception{
        String generateDigest = DigestAuthenticationProvider.generateDigest("user:123456");
        System.out.println(generateDigest);
    }
}

使用介绍

# 创建一个节点
[zk: 127.0.0.1:2181(CONNECTED) 14] create /file file-value
Created /file

# 设置ACL
[zk: 127.0.0.1:2181(CONNECTED) 15] setAcl /file digest:user:6DY5WhzOfGsWQ1XFuIyzxkpwdPo=:crwda
[zk: 127.0.0.1:2181(CONNECTED) 16] getAcl /file
Authentication is not valid : /file

# 添加一个认证用户
[zk: 127.0.0.1:2181(CONNECTED) 17] addauth digest user:123456
[zk: 127.0.0.1:2181(CONNECTED) 18] getAcl /file
'digest,'user:6DY5WhzOfGsWQ1XFuIyzxkpwdPo=
: cdrwa

7.6.4 auth

digest使用时是密文密码,可以直接进行setAcl
auth使用时是明文密码,setAcl前需要先添加认证addauth

# 创建一个节点
[zk: localhost:2181(CONNECTED) 1] create /node
Created /node

# 添加一个认证用户,setAcl前先执行这步
[zk: localhost:2181(CONNECTED) 2] addauth digest user:password

# setAcl
[zk: localhost:2181(CONNECTED) 3] setAcl /node auth:user:password:cdrwa

# getAcl
[zk: localhost:2181(CONNECTED) 4] getAcl /node
'digest,'user:tpUq/4Pn5A64fVZyQ0gOJ8ZWqkY=
: cdrwa

7.6.5 ip

基于客户端IP地址校验,限制只允许指定的客户端能操作znode。
比如,设置某个节点只允许IP为127.0.0.1的客户端能读写该写节点的数据:ip:127.0.0.1:rw

# 创建一个节点
[zk: localhost:2181(CONNECTED) 6] create /newnode
Created /newnode

# setAcl
[zk: localhost:2181(CONNECTED) 7] setAcl /newnode ip:127.0.0.1:cdrwa

# getAcl 
[zk: localhost:2181(CONNECTED) 8] getAcl /newnode 
Authentication is not valid : /newnode

# 认证
[zk: localhost:2181(CONNECTED) 9] addauth digest root:root
[zk: localhost:2181(CONNECTED) 10] getAcl /newnode 
'ip,'127.0.0.1
: cdrwa

8. zookeeper api

java操作zookeeper
pom添加依赖

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.6.2</version>
</dependency>

8.1 连接zookeeper

api 提供的构造方法
构造方法
注意:每个构造器创建连接都是异步的,构造方法启动与服务器的连接,然后立马返回,此时会话处于CONNECTING状态,通过watcher通知。此通知可以在构造方法调用返回之前或之后的任何时候到来。会话创建成功之后,状态会改为CONNECTED。

参数介绍:

参数名 描述
connectString 要创建ZooKeeper客户端对象,应用程序需要传递一个连接字符串,其中包含逗号分隔的host:port列表,每个对应一个ZooKeeper服务器。例如:127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183实例化的ZooKeeper客户端对象将从connectString中选择一个任意服务器并尝试连接到它。如果建立连接失败,将尝试连接字符串中的另一个服务器(顺序是非确定性的,因为是随机),直到建立连接。客户端将继续尝试,直到会话显式关闭。在3.2.0版本之后,也可以在connectString后面添加后缀字符串,如:127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183/app/a,客户端连接上ZooKeeper服务器之后,所有对ZooKeeper的操作,都会基于这个根目录。例如,客户端对/foo/bar的操作,都会指向节点/app/a/foo/bar——这个目录也叫Chroot,即客户端隔离命名空间。
sessionTimeout 会话超时(以毫秒为单位)客户端和服务端连接创建成功之后,ZooKeeper中会建立一个会话,在一个会话周期内,ZooKeeper客户端和服务端之间会通过心跳检测机制来维持会话的有效性,一旦在sessionTimeout时间内没有进行有效的心跳检测,会话就会失效。
watcher 创建ZooKeeper客户端对象时,ZooKeeper允许客户端在构造方法中传入一个接口Watcher(org.apache.zookeeper.Watcher)的实现类对象来作为默认的Watcher事件通知处理器。当然,该参数可以设置为null以表明不需要设置默认的Watcher处理器。如果设置为null,日志中会有空指针异常,但是并不影响使用。
canBeReadOnly 3.4之后添加的boolean类型的参数,用于标识当前会话是否支持“read-only”模式。默认情况下,在ZooKeeper集群中,一个机器如果和集群中过半以上机器失去了网络连接,那么这个机器将不再处理客户端请求(包括读写请求)。但是在某些使用场景下,当ZooKeeper服务器发生此类故障的时候,我们还是希望ZooKeeper服务器能够提供读服务(当然写服务肯定无法提供)——这就是ZooKeeper的“read-only”模式。
sessionId 和 sessionPasswd 会话id和 会话密码,这两个参数能够唯一确定一个会话,同时客户端使用这两个参数实现客户端会话复用,从而达到恢复会话的效果,使用方法:第一次连接上ZooKeeper服务器后,客户端使用getSessionId()和getSessionPasswd()获取这两个值,如果需要会话复用,在重新创建ZooKeeper客户端对象的时候可以传过去,如果不需要会话复用,请使用不需要这些参数的其他构造函数。
HostProvider 客户端地址列表管理器

实例参考:

package cn.sivan.test;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import java.util.concurrent.CountDownLatch;

/**
 * zookeeper的连接和数据库的连接不同,数据库通过DriverManager的getConnect方法就可以直接获取到连接。
 * 但zookeeper在获取连接的过程中,使用了Future。也就意味着,new之后所拿到的仅仅是一个zookeeper对象,而这个对象可能还没有连接到zookeeper服务。
 */
public class Connect implements Watcher {

    private CountDownLatch countDownLatch = new CountDownLatch(1);

    @Override
    public void process(WatchedEvent watchedEvent) {

        if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
            //连接创建成功,唤醒等待线程。
            countDownLatch.countDown();
        }
    }

    /**
     * 连接zookeeper
     * @param connStr address
     * @param timeout 超时时间
     */
    public ZooKeeper connect(String connStr, int timeout) {

        try {
            //创建zookeeper
            ZooKeeper zooKeeper = new ZooKeeper(connStr, timeout, this);

            //当前线程 等待连接连接成功的通知
            countDownLatch.await();
            return zooKeeper;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
}

EventType:Watch事件介绍

  • None
  • NodeCreated
  • NodeDeleted
  • NodeDataChanged
  • NodeChildrenChanged
  • DataWatchRemoved
  • ChildWatchRemoved
  • PersistentWatchRemoved

ZookeeperState:

state 说明
Disconnected 客户端处于断开状态 - 未连接
SyncConnected 客户端处于连接状态 - 已连接
AuthFailed 验证失败状态
ConnectedReadOnly 客户端连接到只读服务器
SaslAuthenticated 客户端已通过 SASL 认证,可以使用其 SASL 授权的权限执行 Zookeeper 操作
Expired 会话已失效
Closed 客户端已关闭,客户端调用时在本地生成

8.2 API使用

package cn.sivan.test;

import org.apache.zookeeper.AddWatchMode;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ZookeeperSimple {

    public static void main(String[] args) throws Exception {
        String path = "/chodble";
        String address = "127.0.0.1:2181";
        int timeout = 3000;

        Connect zkConnect = new Connect();
        ZooKeeper zk = zkConnect.connect(address, timeout);

        //判断节点是否存在
        if (null == zk.exists(path, true)) {

            //设置认证方式
            Id ADMIN_IDS = new Id("digest", DigestAuthenticationProvider.generateDigest("root:root"));
            Id USER_IDS = new Id("digest", DigestAuthenticationProvider.generateDigest("user:user"));
            Id ANYONE_ID_UNSAFE = ZooDefs.Ids.ANYONE_ID_UNSAFE;

            /**
             *设置ACL权限
             *Create  允许对子节点Create 操作
             *Read    允许对本节点GetChildren 和GetData 操作
             *Write   允许对本节点SetData 操作
             *Delete  允许对子节点Delete 操作(本节点也可以删除)
             *Admin   允许对本节点setAcl 操作
             *ALL = READ | WRITE | CREATE | DELETE | ADMIN;
             */
            List<ACL> aclList = new ArrayList<>();
            aclList.add(new ACL(ZooDefs.Perms.ADMIN, ADMIN_IDS));
            //aclList.add(new ACL(ZooDefs.Perms.CREATE | ZooDefs.Perms.READ | ZooDefs.Perms.WRITE | ZooDefs.Perms.DELETE, USER_IDS));
            aclList.add(new ACL(ZooDefs.Perms.READ, ANYONE_ID_UNSAFE));

            /**
             * 创建根节点
             * 不支持递归创建,即无法在父节点不存在的情况下创建一个子节点
             * 一个节点已经存在了,那么创建同名节点的时候,会抛出NodeExistException异常。如果是顺序节点,那么永远不会抛出NodeExistException异常
             * 临时节点不能有子节点
             *
             * CreateMode
             * PERSISTENT : 持久节点
             * PERSISTENT_SEQUENTIAL : 持久顺序节点
             * EPHEMERAL : 临时节点
             * EPHEMERAL_SEQUENTIAL : 临时顺序节点
             * CONTAINER : 容器节点
             * PERSISTENT_WITH_TTL : 持久TTL节点
             * PERSISTENT_SEQUENTIAL_WITH_TTL : 持久顺序TTL节点
             */
            zk.create(path, "宁川".getBytes(), aclList, CreateMode.PERSISTENT);

            //设置Acl
            zk.setACL(path, Collections.singletonList(new ACL(ZooDefs.Perms.CREATE | ZooDefs.Perms.READ | ZooDefs.Perms.WRITE | ZooDefs.Perms.DELETE, USER_IDS)), -1);
        }

        //进行认证
        zk.addAuthInfo("digest", "user:user".getBytes());

        //查看ACL权限
        Stat stat = null;
        if ((stat = zk.exists(path, true)) != null) {
            System.out.println("查看节点权限:" + zk.getACL(path, stat));
        }

        //获取子节点数量
        System.out.println("获取子节点数量:" + zk.getAllChildrenNumber(path));

        //获取所有的节点
        System.out.println("所有的节点:" + zk.getChildren("/", false));

        //获取节点的值,并设置监听
        System.out.println("获取节点的值:" + new String(zk.getData(path, (event -> {
            System.out.println("getWatch:" + event.toString());
        }), stat)));

        //添加监听
        zk.addWatch(path, (event) -> {
            System.out.println("addWatch:" + event.toString());
        }, AddWatchMode.PERSISTENT);

        //设置节点数据
        // 1 -自动维护
        System.out.println("设置节点数据:" + zk.setData(path, "123".getBytes(), -1));

        /**
         * 异步创建一个 临时顺序节点,ACL为 ip:127.0.0.1:c
         */
        zk.create("/node",
                "123".getBytes(),
                Collections.singletonList(new ACL(ZooDefs.Perms.CREATE, new Id("ip", "127.0.0.1"))),
                CreateMode.EPHEMERAL_SEQUENTIAL,
                //new AsyncCallback.StringCallback()
                (rc, path1, ctx, name) -> {
                    System.out.println("rc:" + rc);
                    System.out.println("path:" + path1);
                    System.out.println("ctx:" + ctx);
                    System.out.println("name:" + name);
                }, "传给服务端的内容,会在异步回调时传回来");
        //等待执行结果
        Thread.sleep(2000);

        //删除节点
        if ((stat = zk.exists(path, true)) != null) {
            List<String> subPaths = zk.getChildren(path, false);
            if (subPaths.isEmpty()) {
                zk.delete(path, stat.getVersion());
            } else {
                for (String subPath : subPaths) {
                    zk.delete(path + "/" + subPath, -1);
                }
            }
        }
    }
}

ZooDefs.Ids

public interface Ids {

        /**
         * world:anyone:adrwa
         * This Id represents anyone.
         */
        Id ANYONE_ID_UNSAFE = new Id("world", "anyone");

        /**
         * 认证后可操作
         * This Id is only usable to set ACLs. It will get substituted with the
         * Id's the client authenticated with.
         */
        Id AUTH_IDS = new Id("auth", "");

        /**
         * 完全开放的ACL,任何连接的客户端都可以操作该属性znode
         * This is a completely open ACL .
         */
        @SuppressFBWarnings(value = "MS_MUTABLE_COLLECTION", justification = "Cannot break API")
        ArrayList<ACL> OPEN_ACL_UNSAFE = new ArrayList<ACL>(Collections.singletonList(new ACL(Perms.ALL, ANYONE_ID_UNSAFE)));

        /**
         * 只有创建者才有ACL权限
         * This ACL gives the creators authentication id's all permissions.
         */
        @SuppressFBWarnings(value = "MS_MUTABLE_COLLECTION", justification = "Cannot break API")
        ArrayList<ACL> CREATOR_ALL_ACL = new ArrayList<ACL>(Collections.singletonList(new ACL(Perms.ALL, AUTH_IDS)));

        /**
         * 只读ACL
         * This ACL gives the world the ability to read.
         */
        @SuppressFBWarnings(value = "MS_MUTABLE_COLLECTION", justification = "Cannot break API")
        ArrayList<ACL> READ_ACL_UNSAFE = new ArrayList<ACL>(Collections.singletonList(new ACL(Perms.READ, ANYONE_ID_UNSAFE)));
}

CreateModel

public enum CreateMode {

    /**
     * 持久节点
     * The znode will not be automatically deleted upon client's disconnect.
     */
    PERSISTENT(0, false, false, false, false),

    /**
     * 持久顺序节点
     * The znode will not be automatically deleted upon client's disconnect,
     * and its name will be appended with a monotonically increasing number.
     */
    PERSISTENT_SEQUENTIAL(2, false, true, false, false),

    /**
     * 临时节点
     * The znode will be deleted upon the client's disconnect.
     */
    EPHEMERAL(1, true, false, false, false),

    /**
     * 临时顺序节点
     * The znode will be deleted upon the client's disconnect, and its name
     * will be appended with a monotonically increasing number.
     */
    EPHEMERAL_SEQUENTIAL(3, true, true, false, false),

    /**
     * 容器节点
     * The znode will be a container node. Container
     * nodes are special purpose nodes useful for recipes such as leader, lock,
     * etc. When the last child of a container is deleted, the container becomes
     * a candidate to be deleted by the server at some point in the future.
     * Given this property, you should be prepared to get
     * {@link org.apache.zookeeper.KeeperException.NoNodeException}
     * when creating children inside of this container node.
     */
    CONTAINER(4, false, false, true, false),

    /**
     * 持久TTL节点
     * The znode will not be automatically deleted upon client's disconnect.
     * However if the znode has not been modified within the given TTL, it
     * will be deleted once it has no children.
     */
    PERSISTENT_WITH_TTL(5, false, false, false, true),

    /**
     * 持久顺序TTL节点
     * The znode will not be automatically deleted upon client's disconnect,
     * and its name will be appended with a monotonically increasing number.
     * However if the znode has not been modified within the given TTL, it
     * will be deleted once it has no children.
     */
    PERSISTENT_SEQUENTIAL_WITH_TTL(6, false, true, false, true);
}

9. Watch

一个Watch事件是一个一次性的触发器,当被设置了Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了Watch的客户端,以便通知它们。

watch机制的特点:

  • 一次性触发 数据发生改变时,一个watcher event会被发送到client,但是client只会收到一次这样的信息。
  • watcher event异步发送 watcher 的通知事件从server发送到client是异步的,这就存在一个问题,不同的客户端和服务器之间通过socket进行通信,由于网络延迟或其他因素导致客户端在不通的时刻监听到事件,由于Zookeeper本身提供了ordering guarantee,即客户端监听事件后,才会感知它所监视znode发生了变化。
  • 数据监视 Zookeeper有数据监视和子数据监视 getdata() and exists() 设置数据监视,getchildren()设置了子节点监视
  • 注册watcher getData、exists、getChildren
  • 触发watcher create、delete、setData

常见监听事件:

nodedatachanged # 节点数据改变
nodecreate # 节点创建事件
nodedelete #节点删除事件
nodechildrenchanged # 子节点改变事件
package cn.sivan.test;

import org.apache.zookeeper.AddWatchMode;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;

import java.util.Collections;

public class WatchTest {
    public static void main(String[] args) throws Exception {

        String path = "/chodble2";
        String pathValue = "root-value";

        String subPath = path + "/node";
        String subPathValue = "sbu-value";

        String address = "127.0.0.1:2181";
        int timeout = 3000;

        Connect zkConnect = new Connect();
        ZooKeeper zk = zkConnect.connect(address, timeout);

        //判断节点是否存在
        Stat stat = null;
        if ((stat = zk.exists(path, true)) == null) {

            //创建节点
            zk.create(path, pathValue.getBytes(), Collections.singletonList(new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.ANYONE_ID_UNSAFE)), CreateMode.PERSISTENT);
        }

        //进行认证
        zk.addAuthInfo("digest", "user:user".getBytes());

        //设置节点监听
        zk.addWatch(path, event -> {
            System.out.println("addWatch-1:" + event);
        }, AddWatchMode.PERSISTENT);

        //获取值
        zk.getData(path, true, new Stat());

        //设置值
        zk.setData(path, "new-data".getBytes(), -1);

        //测试通知
        for (int i = 0; i < 10; i++) {
            //重新设置值
            zk.setData(path, ("new-data" + i).getBytes(), -1);
        }

        //设置权限
        zk.setACL(path, Collections.singletonList(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest("user:user")))), -1);

        //创建子节点
        zk.create(subPath, subPathValue.getBytes(), Collections.singletonList(new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.AUTH_IDS)), CreateMode.PERSISTENT);

        //获取子节点的值
        zk.getData(subPath, true, new Stat());

        //设置子节点的值
        zk.setData(subPath, "sub-new-data".getBytes(), -1);

        //设置子节点权限
        zk.setACL(subPath, Collections.singletonList(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest("user:user")))), -1);

        //获取子节点的个数
        zk.getAllChildrenNumber(path);

        //列出所有的子节点
        zk.getChildren(path, true);

        //查看节点ACl
        if ((stat = zk.exists(path, true)) != null) {
            System.out.println("查看节点权限:" + zk.getACL(path, stat));
        }

        //删除子节点
        zk.delete(subPath, -1);

        //删除节点
        zk.delete(path, -1);
    }
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM