一、zookeeper介绍

1、简介

Zookeeper是雅虎公司基于chubby的思想,开发的一个分布式协调组件,后来捐献给Apache公司。它主要用来解决分布式数据一致性问题。分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、配置管理、分布式锁和分布式队列等功能。
zookeeper官方文档:https://zookeeper.apache.org/doc/current/index.html

2、zookeeper工作原理

为了防止单点故障,在使用zookeeper时,一般使用集群。zookeeper集群的工作原理如下图所示:

python使用zookeeper zookeeper.server.principal_中间件


在zookeeper集群中包含三个角色:

Leader:Leader 服务器是整个 zookeeper 集群的核心,主要的工作任务有两项。一、写请求的唯一调度和处理者,保证集群写请求处理的顺序性。二、集群内部各服务器的调度者。

Follower:Follower的职责是:一、处理客户端读请求、转发写请求给 leader 服务器。二、参与写请求 Proposal 的投票(需要半数以上服务器通过才能通知 leader commit 数据; Leader 发起的提案,要求 Follower 投票)。三、参与 Leader 选举的投票

Observer。与Follower一样,但是不参加任何形式的投票

3、zookeeper数据同步流程

zookeeper集群必然会涉及数据同步问题,需要保持各个节点的数据一致性。在zookeeper中,主要依赖 ZAB 协议来实现分布式数据一致性,而ZAB主要分为消息广播、崩溃恢复。

消息广播

zookeeper由Leader节点来接收和处理客户端所有事务请求(写请求),并采用 ZAB 协议的原子广播协议,将事务请求以 Proposal 提议广播到所有 Follower 节点,当集群中有过半的Follower 服务器进行正确的 ACK 反馈,那么Leader就会再次向所有的 Follower 服务器发送commit 消息,将此次提案进行提交。这个过程可以简称为 2pc 事务提交,整个流程可以参考下图,注意 Observer 节点只负责同步 Leader 数据,不参与 2PC 数据同步过程。

python使用zookeeper zookeeper.server.principal_java_02


崩溃恢复

在正常情况消息广播情况下能运行良好,但是一旦 Leader 服务器出现崩溃,或者由于网络原理导致 Leader 服务器失去了与过半 Follower 的通信,那么就会进入崩溃恢复模式,需要选举出一个新的 Leader 服务器。新 leader 将事务日志中尚未提交的消息进行处理。

4、zookeeper的leader选举

zookeeper 的 leader 选举存在两个阶段,一个是服务器启动时 leader 选举,另一个是运行过程中 leader 服务器宕机,需要重新选举leader。
leader选举中的重要参数:
服务器 ID(myid):编号越大在选举算法中权重越大
事务 ID(zxid):值越大说明数据越新,权重越大
逻辑时钟(epoch-logicalclock):同一轮投票过程中的逻辑时钟值是相同的,每投完一次值会增加

zookeeper节点状态:
LOOKING: 竞选状态
FOLLOWING: 随从状态,同步 leader 状态,参与投票
OBSERVING: 观察状态,同步 leader 状态,不参与投票
LEADING: 领导者状态

leader选举
每个节点启动的时候都 LOOKING 观望状态,以三台机器组成的集群为例。第一台服务器 server1启动时,无法进行 leader 选举,当第二台服务器 server2 启动时,两台机器可以相互通信,进入 leader 选举过程:
1、每台 server 发出一个投票,由于是初始情况,server1 和 server2 都将自己作为 leader 服务器进行投票,每次投票包含所推举的服务器myid、zxid、epoch,使用(myid,zxid,epoch)表示,此时 server1 投票为(1,0),server2 投票为(2,0),然后将各自投票发送给集群中其他机器。
2、接收各个服务器的投票。集群中的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票(epoch)、是否来自 LOOKING 状态的服务器。
3、分别处理投票。针对每一次投票,服务器都需要将其他服务器的投票和自己的投票进行对比,对比规则如下:
a. 优先比较 epoch
b. 检查 zxid,zxid 比较大的服务器优先作为 leader
c. 如果 zxid 相同,那么就比较 myid,myid 较大的服务器作为 leader 服务器
4、统计投票结果。每次投票后,服务器会统计投票结果,判断是否有过半机器选出了相同的leader。这里server1、server2 都统计出集群中有两台机器接受了(2,0)的投票信息,此时已经选出了 server2 为 leader 节点。
5、改变服务器状态。一旦确定了 Leader,每个服务器就会更新自己的状态,如果是 Follower,那么就变更为 FOLLOWING,如果是 Leader,就变更为 LEADING

运行过程中的 leader选举
当集群中的 leader 服务器出现宕机或者不可用的情况时,那么整个集群将无法对外提供服务,而是进入新一轮的 Leader 选举,服务器运行期间的 Leader 选举和启动时期的 Leader 选举基本过程是一致的。选举过程:
1、变更状态。Leader 挂后,余下的非 Observer 服务器都会将自己的服务器状态变更为 LOOKING,然后开始进入 Leader 选举过程。
2、每个 Server 会发出一个投票。在运行期间,每个服务器上的 ZXID 可能不同,此时假定 Server1 的 ZXID 为 123,Server3 的 ZXID 为 122;在第一轮投票中,Server1 和 Server3 都会投自己,产生投票(1, 123),(3, 122),然后各自将投票发送给集群中所有机器。接收来自各个服务器的投票。与启动时过程相同。
3、处理投票。与启动时过程相同,此时,Server1 将会成为 Leader。
4、统计投票。与启动时过程相同
5、改变服务器的状态。与启动时过程相同

二、Zookeeper 安装配置

1、linux环境下

下载地址:zookeeper 下载地址为: https://zookeeper.apache.org/releases.html。选择一个稳定版本下载。

python使用zookeeper zookeeper.server.principal_zookeeper_03


官网说明了3.8.0是当前版本,3.7.0是最新的稳定版本,我们下载3.7.0就可以了。

将下载下来的tar包拷贝到linux服务器上。然后进入的tar包所在目录,打开终端。

$ tar -zxvf zookeeper-3.7.0.tar.gz
$ cd zookeeper-3.4.14
$ cd conf/
$ cp zoo_sample.cfg zoo.cfg
$ cd ..
$ cd bin/
$ sh zkServer.sh start

到这里,Zookeeper就配置好启动了,如下所示:

python使用zookeeper zookeeper.server.principal_python使用zookeeper_04


查看服务状态(单机节点):

python使用zookeeper zookeeper.server.principal_中间件_05


启动客户端:

$ sh zkCli.sh

客户端启动后就会进入客户端的操作界面

python使用zookeeper zookeeper.server.principal_服务器_06


在客户端使用help命令,查看客户端的所有命令

python使用zookeeper zookeeper.server.principal_服务器_07

启动完客户端,就可以对Zookeeper服务进行操作了。

2、windows环境下

下载同上。

下载后解压,将 conf 目录下的 zoo_sample.cfg 文件,复制一份,重命名为 zoo.cfg。然后在安装目录下面新建一个空的 data 文件夹和 log 文件夹。修改 zoo.cfg 配置文件,将 dataDir=/tmp/zookeeper 修改成 zookeeper 安装目录所在的 data 文件夹。注意目录不要使用反斜杠(3.7.0版本使用反斜杠会导致目录识别错误)。

python使用zookeeper zookeeper.server.principal_java_08

运行zkServer.cmd启动Zookeeper服务

python使用zookeeper zookeeper.server.principal_python使用zookeeper_09


运行zkCli.cmd启动客户端

python使用zookeeper zookeeper.server.principal_中间件_10

3、zookeeper集群搭建

这里搭建含3个节点的zookeeper集群
步骤:
1、准备好zookeeper安装包,分别复制并解压到三个集群环境里,在conf目录下复制zoo_sample.cfg并重命名为zoo.cfg。
2、配置zoo.cfg文件的dataDir,以及在三个zoo.cfg文件末尾加入server.id = ip:port:port

server.1=IP1:2888:3888 
server.2=IP2.2888:3888
server.3=IP3.2888:3888

ip使用实际ip。2888表示与集群中的节点通信端口,3888表示选举Leader的端口,如果是伪集群,因为ip一致,那么端口应该要不同。
zookeeper 的三个端口作用:
2181 : 对 client 端提供服务
2888 : 集群内机器通信使用
3888 : 选举 leader 使用
配置observer节点时:server.x=host:port1:port2:observer。还需要在zoo.cfg中加入:peerType=observer配置

3、在三个配置的dataDir目录下新建myid文件,每个文件的内容与zoo.cfg配置的id一致,比如server.1的myid 文件内容就是 1。【必须确保每个服务器的 myid 文件中的数字不同,并且和自己所在机器的 zoo.cfg 中 server.id的 id 值一致,id 的范围是 1~255】

4、启动集群。linux下要关闭防火墙,如果是生产环境,则需要打开对应的端口。

三、Zookeeper数据模型znode

1、znode结构和属性

在Zookeeper中,提供了znode的数据模型来实现功能。可以说 zookeeper 中的所有存储的数据是由 znode 组成的,并以 key/value 形式存储数据。整体结构类似于 linux 文件系统的模式以树形结构存储。其中根路径以 / 开头,zookeeper 名称空间中的每个节点都是由一个路径标识。结构如下所示。

python使用zookeeper zookeeper.server.principal_服务器_11


每个节点都有自己的属性,使用get -s 节点全路径或者stat 节点全路径命令可以得到节点的信息。第一行是这个节点的value。

python使用zookeeper zookeeper.server.principal_java_12

属性名称

属性含义

cZxid

该节点被创建时的事务ID

ctime

该节点被创建时的时间

mZxid

该节点最后一次被更新时的事务ID

mtime

该节点最后一次被更新时的时间

pZxid

该节点的子节点列表最后一次更新时的事务ID,注意只有子节点列表变更了才会变更pzxid,子节点内容变更不会影响pzxid

cversion

该节点的子节点版本号,子节点每次更新版本号加1(和pZxid一样,子节点内容不影响)

dataversion

该节点的数据(value)版本号,数据每次修改该版本号加1

aclversion

该节点的权限版本号,权限每次修改该版本号加1

ephemeralOwner

创建该临时节点的会话的sessionID。(如果该节点是持久节点,那么这个属性值为0)

dataLength

该节点的数据长度

numChildren

该节点拥有子节点的数量(只统计直接子节点的数量)

2、znode类型

持久节点(Persistent Nodes)
创建后会一直存在 zookeeper 服务器上,直到主动删除的节点。

create 节点路径 value值

临时节点(Ephemeral Nodes)
临时节点的生命周期和客户端的会话(session)绑定在一起,当客户端会话失效该节点自动清除。临时节点不允许有子节点。使用getEphemerals 节点路径命令可以获得指定路径下的临时节点列表,不加路径则会列出所有临时节点。

//创建临时节点
create -e 节点路径 value值

有序节点(Sequence Nodes)
每个节点都会为它的一级子节点维护一个计数器。这个计数器对于父 znode 是唯一的,计数器的格式为 %010d - 即 10 位数字和 0(零)填充。示例:

//创建的就是node_1下的子节点,节点名称形式:0000000001,值是value
create -s /node/node_1/ value
//创建的就是node下的子节点,节点名称形式就是node_10000000001,值是value
create -s /node/node_1 value

计数器由有符号整数(4 字节)存储,所以超过 2147483647 时,计数器将溢出(导致”-2147483648"出现)。

容器节点(Container Nodes)
当子节点都被删除后,容器节点也随即删除

create -c 节点路径 value值

TTL 节点
可以给持久节点或者持久有序节点设置一个 TTL(以毫秒为单位)。如果节点在 TTL 内没有被修改并且没有子节点,则会被删除。此功能默认是关闭的,需要在配置文件中设置extendedTypesEnabled=true,开启。如果没开启就设置TTL,则Zookeeper服务器将抛出 KeeperException.UnimplementedException。

create -t 1000 节点路径 value值

3、znode特性

1、同一级节点 key 名称是唯一的。
2、创建节点时,必须要带上全路径
3、session 关闭,临时节点清除
4、watch 机制,监听节点变化
5、delete 命令只能一层一层删除,可以使用deleteall 命令递归删除

四、Zookeeper权限控制ACL

Zookeeper使用ACL来控制对znode的访问。ACL实现与UNIX文件访问权限非常相似:它使用权限位来允许/禁止针对节点的各种操作以及这些位适用的范围。与标准 UNIX 权限不同,ZooKeeper 节点不受用户(文件所有者)、组和world(其他)三个标准范围的限制。ZooKeeper 没有 znode 所有者的概念。相反,ACL 指定一组 id 和与这些 id 关联的权限。

ACL命令
getAcl 命令:获取某个节点的 acl 权限信息。
setAcl 命令:设置某个节点的 acl 权限信息。
addauth 命令:输入认证授权信息,注册时输入明文密码,加密形式保存。

Zookeeper 的ACL通过 [scheme🆔permissions] 来构成权限列表。
permissions:权限类型
CREATE:您可以创建一个子节点
READ:您可以从节点获取数据并列出其子节点。
WRITE:您可以为节点设置数据
DELETE:您可以删除一个子节点
ADMIN:您可以设置权限

scheme:权限模式
world:默认权限,只有一个id为“anyone”。

$ create /aclnodes
$ getAcl /aclnodes
//设置world方案,id为anyone,权限为cdrwa
$ setAcl /aclnodes world:anyone:cdrwa
$ getAcl /aclnodes

auth :身份验证模式。需要先通过addauth添加一个验证口令。形式为:user:pwd

//auth:user:pwd:cdrwa
//需要使用addauth先创建用户和密码
$ addauth digest user1:123456
//auth方案,id为user1:123456,权限为cdrwa
$ setAcl /aclnodes auth:user1:123456:cdrwa
$ getAcl /aclnodes

digest:和auth差不多,区别是pwd需要使用加密后的密文,加密方式是BASE64(SHA1(PWD))。形式是digest:user:BASE64(SHA1(PWD)):cdrwa

//digest:user:BASE64(SHA1(PWD)):cdrwa
//HYGa7IZRm2PUBFiFFu8xY2pPP/s=是‘user1:123456’加密后的字符串。可以使用getAcl查看加密后的字符串
$ getAcl /aclnodes
$ setAcl /aclnodes  digest:user1:HYGa7IZRm2PUBFiFFu8xY2pPP/s=:cdrwa
$ getAcl /aclnodes
//其他客户端需要先添加验证口令,才能操作加了此验证口令的节点
$ addauth digest user1:123456
$ getAcl /aclnodes

ip:
使用客户端主机 IP 作为 ACL ID 身份

$ create /ipnode
$ getAcl /ipnode
$ setAcl /ipnode ip:127.0.0.1:cdrwa
$ getAcl /ipnode

还可以直接在创建节点时加上acl

$ create /aclnode1 world:anyone:cdrwa

五、Zookeeper watcher事件

Watcher 监听机制是 Zookeeper 中非常重要的特性。我们基于 zookeeper 上创建的节点,可以对这些节点绑定监听事件,比如可以监听节点数据变更、节点删除、子节点状态变更等
事件,通过这个事件机制,可以基于 zookeeper 实现分布式锁、集群管理等功能。

在客户端命令行中,我们可以使用带有-w参数的命令,设置一个watch监听。
stat path -w :监听节点属性变化
ls path -w:监听子节点列表的变化
get path -w:监听节点的数据变化
示例:

$ create /watchnode
$ get /watchnode -w

当值变化后,就会触发监听:

python使用zookeeper zookeeper.server.principal_python使用zookeeper_13


watch监听只会触发一次,如果要实现永久监听,可以通过循环注册来实现。

六、客户端命令

在客户端输入help命令可以查看所有命令。

python使用zookeeper zookeeper.server.principal_zookeeper_14


常用命令:

命令

作用

ls [-s] [-w] [-R] path

查看节点下的子节点列表 。-s展示属性信息,-w加上监听,-R展示此节点及所有子节点的路径信息

get [-s] [-w] path

用于获取节点数据和状态信息,-s 展示属性信息,-w 加上监听

stat [-w] path

查看节点属性信息。-w加上监听

create [-s] [-e] [-c] [-t ttl] path [data] [acl]

创建节点。-s 有序节点,-e 临时节点, -c 容器节点, -t ttl TTl节点,ttl表示超时时间(ms),acl ACL权限,data 节点数据

set [-s] [-v version] path data

设置节点的值。-s 设置完显示属性信息,-v version 版本号,只有version=当前版本号,才能设置成功。

delete [-v version] path

删除节点,没有子节点时才能删除 -v version 版本号,

七、注意事项

zookeeper3.5版本以后,新增了一个AdminServer功能,这是一个jetty的嵌入式服务器,默认开启,端口就是8080。这会与tomcat冲突。解决方案如下:
1、换端口。在zoo.cfg里新增如下配置。port是不冲突的端口

admin.serverPort=port

2、停用这个服务
在zkServer.cmd/zkServer.sh中加入"-Dzookeeper.admin.enableServer=false"

zkServer.cmd:

python使用zookeeper zookeeper.server.principal_python使用zookeeper_15


zkServer.sh:

python使用zookeeper zookeeper.server.principal_zookeeper_16