从zookeeper官方文档系统学习zookeeper

2023-11-20

从zookeeper官方文档系统学习zookeeper

1. zookeeper

ZooKeeper 是一个集中式服务,用于维护配置信息、命名、提供分布式同步和提供组服务。

ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.
zookeeper官网
下载zookeeper
image-20220228221238058

image-20220228221339680

zookeeper解压的文件目录:

2. zookeeper 文档

zookeeper文档在下载目录的docs目录下。
image-20220228221851741

下载的二进制包里面的文档,一定是最准确的文档。
有时候,比官网文档还准确。
看文档,一般主要是这几个步骤。

  1. welcome或index界面:这里明确的定义了组件的组成、功能以及作用等信息。
  2. Getting Started:这里用一个最简单的例子,教我们入门组件。
  3. config:查询组件全部的配置,以及配置对应的含义。
  4. 集成:这一块主要是组件如何与其他系统进行集成。

一般的文档,都会有这四个部分的。

3. zookeeper 单机版

3.1 配置

单机版的zookeeper需要在conf/zoo.cfg配置三个参数即可。
默认是没有conf/zoo.cfg文件的,需要拷贝zoo_sample.cfg文件。

tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181

tickTime默认就是2000,我们不需要做任何修改。
因为本地启动,我们需要尽可能节省资源,因此将初始化的连接数,调整为2

initLimit=2
syncLimit=2

然后修改zookeeper数据存储路径。
我们在zookeeper的主目录下创建data目录和logs目录
image-20220228222018966

然后在conf/zoo.cfg中配置data目录

dataDir=/D:/Users/JIAYONGQI784/zookeeper/apache-zookeeper-3.7.0-bin/data

log4j.properties中配置日志目录

zookeeper.log.dir=/D:/Users/JIAYONGQI784/zookeeper/apache-zookeeper-3.7.0-bin/logs

OK,到此就配置完成了。

image-20220228222125300

我们顺手把ZOOKEEPER_HOME配置了.
image-20220228222202212
别配置的太深入了,配置的目的是我们可以以非常快速的操作,切换到zookeeper相关的目录即可。

3.2 启动

执行命令

cd %ZOOKEEPER_HOME%
cd bin
zkServer.cmd

当日志没有异常时,就启动成功了
image-20220228222251761

3.3 验证

zookeeper单机版启动成功了,如何验证呢?
在zookeeper的文档中,给出了答案
image-20220228222332803

使用客户端连接zookeeper服务。
重新启动一个cmd,切换到zookeeper的bin目录下。

cd %ZOOKEEPER_HOME%
cd bin
zkCli.cmd -server 127.0.0.1:2181

启动客户端,链接启动的zookeeper服务。
image-20220228222428431

zookeeper客户端的命令

ZooKeeper -server host:port -client-configuration properties-file cmd args
addWatch [-m mode] path # optional mode is one of [PERSISTENT, PERSISTENT_RECURSIVE] - default is PERSISTENT_RECURSIVE
addauth scheme auth
close
config [-c] [-w] [-s]
connect host:port
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
delete [-v version] path
deleteall path [-b batch size]
delquota [-n|-b|-N|-B] path
get [-s] [-w] path
getAcl [-s] path
getAllChildrenNumber path
getEphemerals path
history
listquota path
ls [-s] [-w] [-R] path
printwatches on|off
quit
reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]
redo cmdno
removewatches path [-c|-d|-a] [-l]
set [-s] [-v version] path data
setAcl [-s] [-v version] [-R] path acl
setquota -n|-b|-N|-B val path
stat [-w] path
sync path
version
whoami
Command not found: Command not found help

我们尝试在zookeeper中创建一个hello-world目录,然后创建hi的文件,设置值hi xiaomei!

create /hello-world
create /hello-world/hi
set /hello-world/hi "hi xiaomei\!"
get /hello-world/hi

记得转义特殊符号。

image-20220228222609293

然后删除我们新增的内容

delete /hello-world/hi
delete /hello-world
ls /

至此,我们就搭建了一个单机版的zookeeper.

4. zookeeper 集群版

Running ZooKeeper in standalone mode is convenient for evaluation, some development, and testing. But in production, you should run ZooKeeper in replicated mode.
单机版只适合与开发或者学习,或者测试,并不能在生产环境使用。

4.1 配置

在文档中也有如何搭建集群版的zookeeper。

首先,我们将zookeeper文件拷贝三份出来,并分别修改文件夹名字,加上端口号:

image-20220228223254238

然后修改conf/zoo.cfg文件,增加

image-20220228223243525每个文件都是相同的

server.1=0.0.0.0:2888:3888
server.2=0.0.0.0:2889:3889
server.3=0.0.0.0:2890:3890

If you want to test multiple servers on a single machine, specify the servername as localhost with unique quorum & leader election ports (i.e. 2888:3888, 2889:3889, 2890:3890 in the example above) for each server.X in that server’s config file.

增加端口,注意,每个节点的具体值不同

clientPort=2181

增加日志配置,注意修改路径

dataLogDir=/E:/zookeeper/zookeeper-2181/logs

因为我们有三个节点,因此需要将初始化的连接数修改为3,这样不用等待。

initLimit=3
syncLimit=3

现在出现了一个问题,节点如何知道自己是谁?

The entries of the form server.X list the servers that make up the ZooKeeper service. When the server starts up, it knows which server it is by looking for the file myid in the data directory. That file has the contains the server number, in ASCII.

image-20220228224150681

在data目录下创建一个文件,文件名就是myid,这个文件里面的数字会告诉节点自己是哪个服务。

image-20220228224236055

千万不要有多余的空格等字符

这里数字是结合前面配置的节点列表来配置的,从1开始。(from server.X)

至此,就配置完了。

4.2 启动

切换到对应节点的bin目录下,启动服务即可。(启动前,先将data和logs目录清空,需要重新创建myid文件)

cd %ZOOKEEPER_HOME%
cd apache-zookeeper-3.7.0-bin-2181
cd bin
zkServer.cmd

依次启动三个节点即可,顺序没有关系。

第一个节点启动后,控制台会打印异常:

image-20220228224423809

image-20220228224432920

给其他节点发送消息的时候,连接失败(肯定了啦,还没启动呢)。

启动其他两个节点。

4.3 验证

还是用zkCli.cmd进行验证:

分别用三个客户端连接三个服务器:
image-20220228224832816

然后用一个客户端连接集群:

image-20220228224933472

现在我有4个客户端。

我在第一个客户端上增加目录

image-20220228225000337

在第二个客户端上增加文件,并设置值

image-20220228225111722

第三个客户端上查看文件的值(集群)

image-20220228225135242

在第四个客户端上删除增加的目录和值

image-20220228225205856

证明了三个节点之间是联通的。同时也证明连接集群和连接集群内节点是等价的。

5. zookeeper 配置

在Admin&… 目录下有配置参数说明:

image-20220228225258745

可以看到,虽然zookeeper是3.7.0的版本,但是文档上面还是3.6.
Zookeeper 3.6 Documenttation,看到了吗。
或许可以向zookeeper发起pr。
这个小节就是全部的zookeeper的配置
image-20220228225327995

5.1 最小配置

  • clientPort: 端口
  • secureClientPort:https端口
  • observerMasterPort:master的端口
  • dataDir:数据存储目录
  • tickTime:时间单位(会话连接超时时间)
    在单机版中,使用的配置远远少于此。
    不过这个最小配置是对于zookeeper从节点来说的。
    在zookeeper中,有不同的角色。

5.2 其他配置

配置非常多,但是配置这一块,作为一个字典一样,遇到在这里查就可以了。

image-20220228225417724

之前配置日志目录是在log4j.properties文件中配置的,这样就会导致配置有点分散。
好在zoo.cfg文件中也能够配置日志路径。

image-20220228225500890

6. zookeeper Leader 选举

zookeeper 的选举非常有意思。

在看日志的时候,有一个小技巧:
如果myid与n.sid相同,表示当前节点对外发出通知;
如果myid与n.sid不同,表示当前节点收到其他节点的通知。

我截取了之前搭建的zookeeper集群的部分日志:


server1: LOOKING -> FOLLOWING

2021-05-28 16:20:59,966 [myid:1] - INFO [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):QuorumPeer@1430] - LOOKING
2021-05-28 16:21:16,464 [myid:1] - INFO [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):QuorumPeer@1512] - FOLLOWING

2021-05-28 16:20:59,976 [myid:1] - INFO [WorkerReceiver[myid=1]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:1, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0

2021-05-28 16:21:16,259 [myid:1] - INFO [WorkerReceiver[myid=1]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:2, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:16,261 [myid:1] - INFO [WorkerReceiver[myid=1]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0

2021-05-28 16:21:31,442 [myid:1] - INFO [WorkerReceiver[myid=1]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:FOLLOWING; n.sid:3, n.state:LOOKING, n.leader:3, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0



server2: FOLLOWING -> LEADING

2021-05-28 16:21:16,239 [myid:2] - INFO [QuorumPeer[myid=2](plain=[0:0:0:0:0:0:0:0]:2182)(secure=disabled):QuorumPeer@1430] - LOOKING
2021-05-28 16:21:16,466 [myid:2] - INFO [QuorumPeer[myid=2](plain=[0:0:0:0:0:0:0:0]:2182)(secure=disabled):QuorumPeer@1524] - LEADING

2021-05-28 16:21:16,251 [myid:2] - INFO [WorkerReceiver[myid=2]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:2, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:16,259 [myid:2] - INFO [WorkerReceiver[myid=2]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:1, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:16,262 [myid:2] - INFO [WorkerReceiver[myid=2]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0

2021-05-28 16:21:31,442 [myid:2] - INFO [WorkerReceiver[myid=2]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LEADING; n.sid:3, n.state:LOOKING, n.leader:3, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0



server3: LOOKING -> FOLLOWING

2021-05-28 16:21:31,422 [myid:3] - INFO [QuorumPeer[myid=3](plain=[0:0:0:0:0:0:0:0]:2183)(secure=disabled):QuorumPeer@1430] - LOOKING
2021-05-28 16:21:31,448 [myid:3] - INFO [QuorumPeer[myid=3](plain=[0:0:0:0:0:0:0:0]:2183)(secure=disabled):QuorumPeer@1512] - FOLLOWING

2021-05-28 16:21:31,435 [myid:3] - INFO [WorkerReceiver[myid=3]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:3, n.state:LOOKING, n.leader:3, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:31,442 [myid:3] - INFO [WorkerReceiver[myid=3]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:31,443 [myid:3] - INFO [WorkerReceiver[myid=3]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:2, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:31,446 [myid:3] - INFO [WorkerReceiver[myid=3]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:2, n.state:LEADING, n.leader:2, n.round:0x1, n.peerEpoch:0xc, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:31,446 [myid:3] - INFO [WorkerReceiver[myid=3]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:FOLLOWING, n.leader:2, n.round:0x1, n.peerEpoch:0xc, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0

如果直接看日志,是会觉得有点懵的。
但是你如果按照时间线进行梳理,在结合启动节点的顺序,就能较为清晰的通过日志分析zookeeper的 leader选举过程。


server1: LOOKING -> FOLLOWING

2021-05-28 16:20:59,966 [myid:1] - INFO [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):QuorumPeer@1430] - LOOKING
2021-05-28 16:21:16,464 [myid:1] - INFO [QuorumPeer[myid=1](plain=[0:0:0:0:0:0:0:0]:2181)(secure=disabled):QuorumPeer@1512] - FOLLOWING

2021-05-28 16:20:59,976 [myid:1] - INFO [WorkerReceiver[myid=1]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:1, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0

2021-05-28 16:21:16,259 [myid:1] - INFO [WorkerReceiver[myid=1]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:2, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:16,261 [myid:1] - INFO [WorkerReceiver[myid=1]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0

2021-05-28 16:21:31,442 [myid:1] - INFO [WorkerReceiver[myid=1]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:FOLLOWING; n.sid:3, n.state:LOOKING, n.leader:3, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0



server2: FOLLOWING -> LEADING

2021-05-28 16:21:16,239 [myid:2] - INFO [QuorumPeer[myid=2](plain=[0:0:0:0:0:0:0:0]:2182)(secure=disabled):QuorumPeer@1430] - LOOKING
2021-05-28 16:21:16,466 [myid:2] - INFO [QuorumPeer[myid=2](plain=[0:0:0:0:0:0:0:0]:2182)(secure=disabled):QuorumPeer@1524] - LEADING

2021-05-28 16:21:16,251 [myid:2] - INFO [WorkerReceiver[myid=2]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:2, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:16,259 [myid:2] - INFO [WorkerReceiver[myid=2]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:1, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:16,262 [myid:2] - INFO [WorkerReceiver[myid=2]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0

2021-05-28 16:21:31,442 [myid:2] - INFO [WorkerReceiver[myid=2]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LEADING; n.sid:3, n.state:LOOKING, n.leader:3, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0



server3: LOOKING -> FOLLOWING

2021-05-28 16:21:31,422 [myid:3] - INFO [QuorumPeer[myid=3](plain=[0:0:0:0:0:0:0:0]:2183)(secure=disabled):QuorumPeer@1430] - LOOKING
2021-05-28 16:21:31,448 [myid:3] - INFO [QuorumPeer[myid=3](plain=[0:0:0:0:0:0:0:0]:2183)(secure=disabled):QuorumPeer@1512] - FOLLOWING

2021-05-28 16:21:31,435 [myid:3] - INFO [WorkerReceiver[myid=3]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:3, n.state:LOOKING, n.leader:3, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:31,442 [myid:3] - INFO [WorkerReceiver[myid=3]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:31,443 [myid:3] - INFO [WorkerReceiver[myid=3]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:2, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0xb, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:31,446 [myid:3] - INFO [WorkerReceiver[myid=3]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:2, n.state:LEADING, n.leader:2, n.round:0x1, n.peerEpoch:0xc, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0
2021-05-28 16:21:31,446 [myid:3] - INFO [WorkerReceiver[myid=3]:FastLeaderElection$Messenger$WorkerReceiver@390] - Notification: my state:LOOKING; n.sid:1, n.state:FOLLOWING, n.leader:2, n.round:0x1, n.peerEpoch:0xc, n.zxid:0xb000001c4, message format version:0x2, n.config version:0x0


20:59.966 -> server1 # LOOKING
20:59.976 -> L1: sid = 1, leader = 1, peerEpoch = 0xb, zxid = 0xb000001c4 => server2 : timeout => server3 : timeout

21:16.239 -> server1 # LOOKING, server2 # LOOKING
21:16.251 -> L2: sid = 2, leader = 2, peerEpoch = 0xb, zxid = 0xb000001c4 => server1 : success => server3 : timeout
21:16.259 -> L2: server2 => server1 @ sid = 2, leader = 2, peerEpoch = 0xb, zxid = 0xb000001c4 $$ sid : 2 > 1 ==> server1 accept ----> sid = 1, leader = 2, peerEpoch = 0xb, zxid = 0xb000001c4
21:16.261 -> L2: server1 => server2 @ sid = 1, leader = 2, peerEpoch = 0xb, zxid = 0xb000001c4 $$ server1 accept server2 is leader.
21:16:262 -> L2: sid = 1, leader = 2, peerEpoch = 0xb, zxid = 0xb000001c4 => server1 LOOKING
21:16,464 -> L2: server1 --> LOOKING -> FOLLOWING
21:16,466 -> L2: server2 --> LOOKING -> LEADING
21:16,466 -> server1 # LOOKING, server2 # LEADING

21:31,422 -> server3 # LOOKING
21:31,435 -> L3: sid = 3, leader = 3, peerEpoch = 0xb, zxid = 0xb000001c4 => server1 : success => server2 : success
21:31,442 -> L3: server1 => server3 @ sid = 3, leader = 3, peerEpoch = 0xb, zxid = 0xb000001c4 $$ FOLLOWING
21:31,442 -> L3: server2 => server3 @ sid = 3, leader = 3, peerEpoch = 0xb, zxid = 0xb000001c4 $$ LEADING
21:31,442 -> L3: server3 => server1 @ sid = 1, leader = 2, peerEpoch = 0xb, zxid = 0xb000001c4 $$ leader = 2
21:31,443 -> L3: server3 => server2 @ sid = 2, leader = 2, peerEpoch = 0xb, zxid = 0xb000001c4 $$ leader = 2
21:31,446 -> L3: server3 => server2 @ sid = 2, leader = 2, peerEpoch = 0xb, zxid = 0xb000001c4 $$ server2 -> LEADING # peerEpoch = 0xc
21:31,446 -> L3: server3 => server1 @ sid = 1, leader = 2, peerEpoch = 0xb, zxid = 0xb000001c4 $$ server1 -> FOLLOWING # peerEpoch = 0xc
21:31,448 -> server3 # FOLLOWING, server2 # LEADING, server1 # FOLLOWING

首先,集群中的zookeeper节点有4种状态:

  1. LOOKING: 寻找Leader状态,处于该状态需要进入选举流程
  2. LEADING:领导者状态,处于该状态的节点说明已经是Leader了
  3. FOLLOWING:跟随者状态,表示Leader已经选举出来了,当前节点跟随Leader
  4. OBSERVER:观察者状态,观察者状态的节点不参与Leader选举

6.1 启动时的Leader选举

分析三个节点的zookeeper启动时的Leader选举:

server1启动,此时server1的状态是LOOKING状态,然后server1进入选举流程。
然后server1启动Leader选举流程

image-20220228225639406

首先server1选举自己作为Leader,同时把自己的状态LOOKING通知给已知节点(配置文件中配置的节点),并通知其他节点自己的选举事务id(使用选举讨论次数可能会好理解点)

Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:1, n.round:0x1, n.peerEpoch:0x0, n.zxid:0x0, message format version:0x2, n.config version:0x0

因为Leader选举需要其他节点投票认可才能成为Leader,此时只有server1节点,是无法获得投票的,所以server1一直处于LOOKING状态,也一直无法选举成功。

接着server2启动了。
server2启动后,状态是LOOKING状态,将进入选举流程。
server2启动选举

image-20220228225808145

Notification: my state:LOOKING; n.sid:2, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0x0, n.zxid:0x0, message format version:0x2, n.config version:0x0

server2选举自己作为Leader,同时把自己的状态LOOKING通知给已知节点。

需要注意的一点是,在server2进行选举的时候,server1也在进行选举。
server2启动后,将自己的选举信息通知给server1,期望获取server1的投票。

image-20220228225856616

Notification: my state:LOOKING; n.sid:2, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0x0, n.zxid:0x0, message format version:0x2, n.config version:0x0

server1收到server2的通知后,发现server2除了n.leader与server1不同外,没有其他的区别。
此时server1和server2竞争leader。
但是因为server1的myid小于server2的myid。也就是n.sid.所以,server1只能同意server2的选举。
于是server1通知其他节点自己的投票

image-20220228230242370

Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0x0, n.zxid:0x0, message format version:0x2, n.config version:0x0

server1的投票中,n.leader:2表示server1选择server2作为leader。

当然,在leader选举出来之前,server1和server2是平等的,server1也会向server2发送通知,期望获取server2的投票

image-20220228230204562

Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:1, n.round:0x1, n.peerEpoch:0x0, n.zxid:0x0, message format version:0x2, n.config version:0x0

server2在收到server1要求投票的通知后,发现server1和server2的除了n.leader不同,其他的都相同。
但是因为server2的sid大于server1的sid,因此,server2不会将票投给server1,也就不会发送投票结果了。

server2收到server1的投票后,会进行统计票数(实际上每一个节点都会统计)

image-20220228230353041

Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0x0, n.zxid:0x0, message format version:0x2, n.config version:0x0

此时server2已经有2个节点支持作为leader了。

在等待了一个会话时间段后(也就是配置文件中配置的tickTime),没有收到其他节点的通知,表示当前集群中的节点都已投票。

然后集群内的全部节点进行切换。
leader节点切换为LEADING状态,其他节点切换为FOLLOWING状态(OBSERVER节点除外)

image-20220228230445739

image-20220228230510289

当server3节点启动后,server3是LOOKING状态,进入leader选举流程。
server3选举自己作为leader,将信息通知给已知节点,期望获取其他节点的投票。

image-20220228230550934

Notification: my state:LOOKING; n.sid:3, n.state:LOOKING, n.leader:3, n.round:0x1, n.peerEpoch:0x0, n.zxid:0x0, message format version:0x2, n.config version:0x0

server1和server2在收到通知后,将现在的leader信息和自己的状态信息发送给server3
server1收到的通知:

image-20220228230647124

Notification: my state:FOLLOWING; n.sid:3, n.state:LOOKING, n.leader:3, n.round:0x1, n.peerEpoch:0x0, n.zxid:0x0, message format version:0x2, n.config version:0x0

收到通知后,因为现在的leader是2,所以server1将自己的投票结果通知给server3.

image-20220228230913274

Notification: my state:LOOKING; n.sid:1, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0x0, n.zxid:0x0, message format version:0x2, n.config version:0x0

同样的server2也会收到server3要求投票的通知:

Notification: my state:LEADING; n.sid:3, n.state:LOOKING, n.leader:3, n.round:0x1, n.peerEpoch:0x0, n.zxid:0x0, message format version:0x2, n.config version:0x0

server2也会将自己的投票结果发送给server3

Notification: my state:LOOKING; n.sid:2, n.state:LOOKING, n.leader:2, n.round:0x1, n.peerEpoch:0x0, n.zxid:0x0, message format version:0x2, n.config version:0x0

此时投票完成。

接着server3收到了server1和server2的状态同步通知

image-20220228230823691

Notification: my state:LOOKING; n.sid:1, n.state:FOLLOWING, n.leader:2, n.round:0x1, n.peerEpoch:0x1, n.zxid:0x0, message format version:0x2, n.config version:0x0
Notification: my state:LOOKING; n.sid:2, n.state:LEADING, n.leader:2, n.round:0x1, n.peerEpoch:0x1, n.zxid:0x0, message format version:0x2, n.config version:0x0

每个节点会知道集群内每一个节点的状态。

server3修改自己的状态,跟随现有leader。
因为目前集群中只有3个节点,server2作为leader得到了2个节点的投票,满足了半数原则(超过一半同意)。

image-20220228231009528

此时,整个集群已经完全OK了。

不知道你是否有这样的疑问,为什么每一次投票或者选举后,都需要发送通知呢?
因为在实际的集群中,不仅仅有leader节点,还有Observer节点。
做的例子中,每一个节点都可以参与选举,实际使用中,可能部分节点才能参与选举,其他节点是不会参与选举的。不参与选举,但是可以参与投票。
所以就需要通知选举信息给这些节点,这些不参与选举的节点就会进行投票统计,然后根据投票结果进行跟随。

6.2 运行中的leader选举

在6.1中的zookeeper集群中,如果leader下线,此时会触发Leader选举,然后根据myid确定真正的leader。
所以,如果让server2下线,重新选举后,leader应该是server3.
试试:

image-20220228231145333

image-20220228231221387

成功证实了的猜测。

6.3 zookeeper 节点状态

  1. LOOKING

处于LOOKING状态的节点将进行Leader选举流程。

LOOKING状态是zookeeper节点启动时处于的状态。

LOOKING状态是一个临时性的zookeeper节点状态。不是稳定状态。

LOOKING状态可以转为LEADING和FOLLOWING,FOLLOWING也可以转为LOOKING。

同样的,LEADING也可以转为LOOKING。(小集群加入大集群)

  1. LEADING

LEADING状态的节点在zookeeper集群中是唯一的。

一个zookeeper集群中,只会允许有一个LEADING状态的节点。(保证集群内的节点网络互通)

LEADING状态的节点会协调并管理(数据层面)FOLLOWING节点

  1. FOLLOWING

FOLLOWING状态的节点在zookeeper集群中占大多数,用于处理客户端的请求。

FOLLOWING状态的节点接受LEADING节点的调度,执行一些事务方面的操作。

  1. OBSERVER

OBSERVER节点是观察者节点,观察的对象是LEADING。

主要是用来减少LEADING的压力,因为OBSERVER与LEADING的数据完全相同,因此一些节点的信息同步,或者数据读取就会分配给OBSERVER,通过这种方式减轻LEADING的压力

7. zookeeper 客户端命令

7.0 文档

官方文档是学习zookeeper的字典。

image-20220228231313812

7.1 zookeeper 客户端连接

连接zookeeper服务

常用格式:

zkCli.cmd -server host:port

image-20220228231335028

7.2 create

创建节点。在zookeeper中,可以将节点等价于文件系统中的文件夹或文件。

命令格式:

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

  • -s : 创建顺序节点
  • -e : 创建临时节点
  • -c :创建存储于指定服务器的节点
  • -t :创建超时节点,需要set zookeeper.extendedTypesEnabled=true
  • path :节点路径
  • data :节点数据
  • acl :节点权限

注意点:

  1. 创建节点需要一层一层创建
  2. 临时节点不能有子节点
  3. 创建超时节点需要开启配置zookeeper.extendedTypesEnabled=true
  4. 创建顺序节点,创建的节点会自动增加排序值
  5. 创建节点时赋值,需要注意转义
  6. 创建节点有默认的访问权限

7.3 ls

查看节点结构。功能与linux的ls相同。

命令格式

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

  • -s: 显示详细信息
  • -R:显示子节点
  • -w:设置子节点监视器

7.4 get

获取节点的值。功能与cat类似。

命令格式

get [-s] [-w] path

  • -s:显示详细信息
  • -w:显示监视器

7.5 set

设置节点的值。

命令格式

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

  • -s :显示详细信息
  • -v:设置版本号(cas:comple and set)
  • path:节点路径
  • data:节点值

7.6 delete

删除节点

命令格式

delete [-v version] path

  • -v:版本号(cas)
  • path:节点路径

命令格式

deleteall path [-b batch size]

  • path:节点路径
  • -b:节点数量

deleteall可以删除含有子节点的节点

delete必须删除子节点

8. zookeeper 权限ACL

8.1 ACL文档

zookeeper 权限的文档

image-20220228231418706

8.2 ACL命令

getAcl:获取某个节点的权限信息

setAcl:设置某个节点的权限信息

create:创建节点的时候,也可以同时设置权限信息

addAuth:认证授权信息(配合权限使用,类似登录)

8.3 ACL组成

[schema:id:permissions]

  • schema: 使用哪种权限机制(可以理解为模式)
  • id: 权限认证的id
  • permissions: 权限(类似linux中的读写执行权限)

8.3.1 permissions

  1. create:创建子节点,缩写c
  2. read:读取节点和子节点的值,缩写r
  3. write:给节点赋值,缩写w
  4. delete:删除节点或子节点,缩写d
  5. admin:给节点设置权限,缩写a

8.3.2 schema

  1. world

默认权限,world只有一个id可用,anyone。

默认任何人拥有全部的权限

image-20220228231524443

  1. auth

授权节点的指定权限给指定用户。

先需要创建用户

image-20220228231606641

然后进行授权

image-20220228231717528

此时设置值

image-20220228231742130

在启动一个客户端访问

image-20220228231829718

登录,即可访问

image-20220228231904543

auth 仅仅授权给当前认证的用户,也就是说,在未认证用户前,使用auth将会失败:

image-20220228232243335

digest 可以授权给任何用户

  1. digest

与auth非常类似,区别在于,digest可以授权给任何用户。

授权的时候,使用 digest:user:MD5(password):crdwa

MD5(123456) = 6DY5WhzOfGsWQ1XFuIyzxkpwdPo=

image-20220228232517249

然后用其他客户端登录并访问

image-20220228232609200

  1. ip

限制客户端的ip,只有客户端使用指定的ip才能访问指定的操作

  1. x509

没用过,不知道干啥的。

9. zookeeper 监视器

9.1 zookeeper 监视器 文档

image-20220228232633980

9.2 zookeeper 客户端命令使用监视器

image-20220228232724649

addWatch [-m mode] path # optional mode is one of [PERSISTENT, PERSISTENT_RECURSIVE] - default is PERSISTENT_RECURSIVE

查看cli的客户端命令,找到和监视器有关的命令:
addWatch [-m mode] path

printwatches on|off

9.3 zookeeper 监视器特点

  • 一次触发: 当数据发生变化,达到监视器的条件后,只会触发一次,并不会重复发送触发消息。除非数据发生改变,且再一次达到监视器的条件。
  • 发送给客户端:当监视器被触发后,zookeeper服务端会发送触发事件给客户端。这些发送是异步的。但是服务端会保证每一个客户端接收的事件的顺序是一致的,时间可能不同(和客户端的网络等因素有关)
  • 轻量级:事件通知只是会告诉客户端发生了事件,但是变更的内容并不会提供。

因为一次触发的特性,导致了:如果客户端短暂的和服务端失去了链接,那么,在失去链接的这段时间内触发的监视器,在过了重试次数和链接时间后,客户端将丢失这期间的事件触发。

9.4 zookeeper 监视器事件类型

  1. NodeCreated : 节点创建
  2. NodeDataChanged : 节点数据发生变更
  3. NodeChildrentChanged : 子节点下发生变更
  4. NodeDeleted : 节点删除

9.5 zookeeper 客户端命令使用监视器

记得开启打印watches

image-20220301000511865

image-20220301000737861

image-20220301000902364

9.6 zookeeper 程序使用监视器

import org.apache.curator.utils.DefaultZookeeperFactory;
import org.apache.zookeeper.AddWatchMode;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

/**
 * @author jiayq
 * @Date 2022-03-01
 */
public class Watch1 {

    public static void main(String[] args) throws Exception {
        Watcher watcher = watchedEvent -> {
            System.out.println("watched event : " + watchedEvent.getType().name());
            switch (watchedEvent.getType()) {
                case NodeCreated:
                    System.out.println(" node create : " + watchedEvent.getPath());
                    break;
                case NodeDataChanged:
                    System.out.println(" data changed : " + watchedEvent.getPath());
                    break;
                case DataWatchRemoved:
                    System.out.println(" data remove : " + watchedEvent.getPath());
                    break;
                case NodeDeleted:
                    System.out.println(" node remove : " + watchedEvent.getPath());
                    break;
                case NodeChildrenChanged:
                    System.out.println(" node children changed : " + watchedEvent.getPath());
                    break;
                case ChildWatchRemoved:
                    System.out.println(" node children remove : " + watchedEvent.getPath());
                    break;
                default:
                    System.out.println(watchedEvent.getType().name() + " : " + watchedEvent.getPath());
                    break;
            }
        };
        ZooKeeper zooKeeper = new DefaultZookeeperFactory()
                .newZooKeeper("127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183", 6000, sx -> {
                }, false);
        zooKeeper.addWatch("/", watcher, AddWatchMode.PERSISTENT_RECURSIVE);
        System.in.read();
    }

}

image-20220301002251044

10. zookeeper 数据结构

10.1 zookeeper 存储数据

在zookeeper 中,的数据都是存储在一个个的路径下面的,而这些路径就类似于linux的文件路径一样。

在linux中是文件夹和文件,而在zookeeper中,则被统一称为节点。

节点可以存储数据。但是,一个节点只能存储一个数据。如果给同一个节点赋值多次,那么已最后一次为准。

节点下面可以有下级节点。所有的节点都是从/节点生长出来的。

使用ls,get可以查看一个节点的详细信息

image-20220301002342774

10.2 zookeeper节点

在zookeeper中,每一个节点都可以由这些属性描述

属性名字 属性释义
cZxid 创建节点的事务id
ctime 创建节点的时间
mZxid 最后修改节点时的事务id
mtime 最后修改节点的时间
pZxid 节点的子节点列表发生修改的事务id(增加或者删除子节点,修改子节点的数据不会修改子节点列表)
cversion 子节点版本号,子节点每次修改版本号+1(只统计直属子节点)
dataversion 数据版本号,数据每次修改版本号+1
aclversion 权限版本号,权限每次修改版本号+1
ephemeralOwner 创建该临时节点的会话的sessionId(如果是持久节点,这个属性值为0)
dataLength 节点数据长度
numChildren 该节点的子节点数量(直属子节点)

11. zookeeper session

客户端与服务端之间的连接是基于tcp连接的。

这个tcp连接,就是一个session会话。

每个session会话,zookeeper服务端都会分配一个sessionId,这个sessionId全局唯一,并且不会重复。

分桶管理策略

zookeeper会根据当前时间,计算下次超时时间。在超时时间内,客户端如果给服务端成功发送了读取、写入或者ping数据包,那么zookeeper就会将这个客户端的会话移动到下一次超时的列表中。

可以将时间按照超时时间,划分为一个个的时间区间(桶)

每当在时间区间内,有客户端成功发送了(成功发送,表示服务端成功读取哦)读取、写入或者ping数据包,那么zookeeper就会将会话移动到下一个区间。

这样,当时间到了下一个区间内,下一个区间内的会话都可用,而没有移动到下一个区间内的会话,则会被关闭。

会话超时时间默认是tickTime。(也就是必须要配置的几个参数之一)

需要注意的是,实际上zookeeper并不是一个时间区间一个时间区间移动session的,而是会考虑会话超时时间和检测间隔时间进行跨多个时间分区进行迁移。

举个例子吧:

会话超时时间:sessionTimeout 为 3秒

会话检测时间:tickTime为1秒

那么在迁移的时候,就是每3秒迁移次,此时迁移跨越了3个时间区间。

12. zookeeper 数据同步

zookeeper 有一个非常重要的功能,就是为分布式应用提供一致性服务。

但是zookeeper本身也是分布式应用,那么zookeeper是如何保证自身数据的一致性的,并对外提供一致性服务?

12.1 ZAB协议

image-20220301002500359

ZAB协议全称:Zookeeper Atomic Broadcast(zookeeper原子播送)

很多资料,张口ZAB,闭口ZAB,但是ZAB究竟是什么?看看全称就比较清晰了。(当你熟悉的时候使用简称无可厚非,但是刚接触的时候,知道全称非常有助于理解)

但是,ZAB协议到底是个啥?

At the heart of ZooKeeper is an atomic messaging system that keeps all of the servers in sync.

这是文档中对于ZAB协议的说明。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从zookeeper官方文档系统学习zookeeper 的相关文章

随机推荐

  • MATLAB求解峰值、均值、平均幅值、方差、均方根、方根幅值、最值、峭度、斜度、裕度指标、波形指标、脉冲指标、峰值指标、峭度指标

    MATLAB求解峰值 均值 平均幅值 方差 均方根 方根幅值 最值 峭度 斜度 裕度指标 波形指标 脉冲指标 峰值指标 峭度指标 附代码前 先把代码中的符号注释一下 峰值fz 均值jz Mean error 平均幅值pf 方差fc 均方根j
  • Publish Over SSH插件的使用(jenkins远程服务器发送文件)

    Publish Over SSH ssh插件 用于连接ssh服务器 然后在该服务器上做一些操作 例如连接服务器 部署服务到该服务器上 1 在插件库中查找安装 2 Manager Jenkins gt 系统配置 gt Publish over
  • 为什么都劝运维转开发,却仍看到大批年轻人运维做的风生水起?

    当谈到运维职业发展情况时 很多人都会说运维做不长久 然后劝人做两年就赶快转研发吧 总之是全面唱衰运维 但作为一个老运维 我想说的是 运维转开发确实是一个不错的选择 但运维做不长久则完全是对运维的偏见了 很多人有运维做不长久的偏见的原因其实和
  • 计算机转本考试题,转本”计算机应用基础统一考试试题

    2009年 专转本 计算机应用基础统一考试试题 考试时间90分钟 满分100分 一 单项选择题 1 下列关于集成电路的叙述 正确的是 A 集成电路的集成度将永远符合Moore定律 B 集成电路的工作速度主要取决于组成逻辑门电路的晶体管的尺寸
  • eclipse的全局替换快捷键

    Ctrl H 一 选择需要替换的内容 二 快捷键Ctrl H 选择 File Search gt Replace 三 替换新内容
  • Pycharm 高效编码技巧--模块自动导入

    磨刀不误砍柴工 欢迎大家来到我的博客 Pycharm 拥有丰富的插件 快捷键 具有重构功能和调试技巧 当然是Python编码首选的IDE 不管你导入啥模块 你是回到页头 手动imoprt 喜欢偷懒的我 绝不放弃任何偷懒的技巧 一起来配置一下
  • redis精选面试题2022(持续更新)

    目录 一 redis是单线程还是多线程 二 Redis 为什么用单线程 三 redis是单线程为什么还这么快 1 内存数据库 2 简单的数据结构 3 单线程 4 IO多路复用 四 redis是如何使用跳表来存储的 五 redis没有设置过期
  • [HDLBits] Exams/ece241 2014 q7a

    Design a 1 12 counter with the following inputs and outputs Reset Synchronous active high reset that forces the counter
  • Linux下Nginx编译安装后的开机自启动设置

    Linux下Nginx编译安装后的开机自启动设置 一 查看当前Nginx启动状态 二 而配置Nginx相关服务文件 三 设置nginx命令 四 设置开机启动 五 测试开机启动 一 查看当前Nginx启动状态 root node1 sbin
  • Python编程从入门到实践(六)-用户输入和while循环

    1 函数input 的工作原理 函数input 让程序暂停运行 等待用户输入一些文本 获取用户输入后 Python将其存储在一个变量中 以方便以后使用 例如 下面的程序让用户输入一些文本 再将这些文本呈现给用户 parrot py mess
  • QT5.15.2+MSVC2022编译程序记录

    问题描述及简单分析 使用MinGW在windows上编译的release版exe 然后再使用windeployqt进行打包关联库 在没有msvc环境的电脑中运行还是会出现报错和某些不确定的问题 比如调用pysttx3包的时候 无法正确加载语
  • js仿苹果风格弹出框alert插件

    下载地址 js仿苹果风格弹出框alert插件 多种调用方式 dd
  • Jenkins+Docker持续集成 流程图

    简介 在互联网时代 对于每一家公司 软件开发和发布的重要性不言而喻 目前已经形成一套标准的流程 最重要的组成部分就是持续集成 CI 及持续部署 交付 CD 本文基于Jenkins Docker Git实现一套CI自动化发布流程 传统工作流程
  • 对对对,我放出来了,对对对对对对--对对对对!

    很多同学以为我懒 天天问我怎么一直不发文 是不是懒了 胖了 堕落了 被收变了 其实主要因为我没背景 只有背影 黑屋去吧你 我刚从黑屋出来了 最近发生这么多事 我还说什么 不说了 一切都是对对对对对对对 就对了 还有很多同行的号直接祭了 最近
  • js读取jpg图片扩展信息,处理二进制数据

    js 读取图片扩展信息 将图片转为ArrayBuffer var http new XMLHttpRequest http onload function if this status 200 this status 0 console l
  • ReactNative D8 Cannot fit requested classes in a single dex file

    我现在的版本 再更新RN版本之后 发现在装包的时候出现了 D8 Cannot fit requested classes in a single dex file methods 65749 gt 65536 这个问题 百度上是说Andro
  • 正弦函数的频谱图(FFT)

    从理论上讲 正弦函数的傅里叶变换是冲击函数 它的幅值为原正弦信号幅值的1 2倍 即 若x t Acos t 则其频谱幅值最大值为A 2 如左图 但是 我们用matlab求出来的频谱图却不是左边这样的 而是右图 原因是 1 理论中的正弦信号是
  • 分布式时序相似查询初探

    时序数据 即随时间变化的数据 在人们的日常生活中无处不在 过去的近十年来 随着电子监控和智能穿戴等设备的普及 更是产生了海量的时序数据 例如 经过多年的发展 火力发电行业的数字化程度已经达到了很高的水平 以一台60万千瓦的中型火电机组为例
  • c#复习题(简答题:基础+面向对象)

    1 简答题 填写程序 要求如下 输入三角形三条边 先判断是否可以构成三角形 如果可以 则求三角形的周长和面积 否则报错 提示 输入的三个数构成三角形三边的条件 每条边长均大于0 并且任意两边之和大于第三边 代码编写 static void
  • 从zookeeper官方文档系统学习zookeeper

    从zookeeper官方文档系统学习zookeeper 1 zookeeper 2 zookeeper 文档 3 zookeeper 单机版 3 1 配置 3 2 启动 3 3 验证 4 zookeeper 集群版 4 1 配置 4 2 启